Управление состоянием компонента контейнера с помощью RxAngular

RedDeveloper
18.02.2023 12:02
Управление состоянием компонента контейнера с помощью RxAngular

Мы должны уделять больше внимания важности управления состоянием компонента. Этому не уделяется должного внимания.

Чтобы сделать ваш компонент как можно более тонким, я предлагаю абстрагировать состояние компонента в виде фасада . Основная идея здесь заключается в том, чтобы внедрить сервисы компонентного уровня для хранения бизнес-сущности, изображенной в вашем компоненте-контейнере.

State | RxAngular.

Да, на этот раз мы будем использовать вышеупомянутую библиотеку, чтобы продемонстрировать то же самое на следующем примере:

  • постраничный (бесконечная прокрутка) список открытых событий
  • Вывод постраничного (бесконечная прокрутка) списка закрытых событий
  • Когда пользователь закрывает любое открытое событие, добавьте его в список закрытых событий с несколькими управляемыми свойствами и одновременно удалите его из списка открытых событий.

Примеры выполнения для этой статьиРендеринг paginate

Демонстрация двух списков, которые каким-то образом связаны между собой
Демонстрация двух списков, которые каким-то образом связаны между собой

Состояние для открытых событий

// state for the open events component
{
  count: number;
  pageNumber: number;
  events: IUserEvent[];
}

Определение событий, которые необходимо извлечь

Зависит от текущей страницы и идентификатора пользователя, для которого нужно получить события

userEvents$ = combineLatest([
  this.userId$, // observable for retrieving userId
  this.select('pageNumber'), // this is how we listen to a peice of state in RxState
]).pipe(
    switchMap(([userId, pageNumber]) =>
      this.fetchUserEvents(userId, pageNumber) // API call to fetch user events
    ),
    map(({ data }) => data)
  );

Поддержание состояния userEvents для каждого выдаваемого набора значений

this.connect(this.userEvents$, (oldState, newEmittedValue) => {
    if (oldState.pageNumber > 1) { // for pagination
        return {
            events: oldState.events.concat(newEmittedValue.events),
        };
    }
    return value; // reset the state to the new emitted value (page 1 events)
});

Существует также действие (закрытие этих событий), давайте определим его

// Action to be fired from within the component
  closeEvents$$ = new Subject<{
    eventsToClose: IUserEvent[];
    resolution: Record<'reason' | 'note', string>;
  }>();

События закрыты, выполняется вызов API обновления и помечаются как закрытые. Также они должны быть вычтены из коллекции открытых событий. Это порождает связанное состояние (eventsClosed$) для прослушивания, которое должно сработать после закрытия событий.

eventsClosed$ = this.closeEvents$$.pipe( // on closeEvents$$ action
    exhaustMap(({ eventsToClose, resolution }) =>
      this.closeEvents(eventsToClose, resolution) // Api call to persist the change
    ),
    share()
  );

Манипулирование состоянием после закрытия выбранных событийКаждый раз, когда выбранное событие закрыто

this.connect(this.eventsClosed$, (oldState, value) => {
      const closedEventsIdSet = value.reduce(
        (acc, e) => acc.add(e.id),
        new Set()
      );

      // updated state
      return {
        events: oldState.events.filter(
          (oEvent) => closedEventsIdSet.has(oEvent.id) === false
        ),
        count: oldState.count - value.length,
      };
    });

Аналогичным образом можно сформировать фасад состояния и для списка закрытых событий

interface ICloseEventsState {
  count: number;
  pageNumber: number;
  events: IUserEvent[];
}

enum EventStatus {
  open = 0,
  close = 1,
}

@Injectable()
export class CloseEventsState extends RxState<ICloseEventsState> {
  readonly closeEvents$ = this.select('events');
  readonly totalCount$ = this.select('count');

  private http = inject(HttpClient);
  private store = inject(Store);
  private userId$ = this.store.select(USER_STATE_TOKEN).pipe(
    map(({ id }) => id),
    distinctUntilChanged()
  );

  private userClosedEvents$ = combineLatest([
    this.userId$,
    this.select('pageNumber'),
  ]).pipe(
    switchMap(([userId, pageNumber]) =>
      this.fetchUserEvents(userId, pageNumber)
    ),
    map(({ data }) => data)
  );

  constructor(private openEventsState: OpenEventsState) {
    super();
    this.set({
      pageNumber: 1,
    });

    this.connect(this.userClosedEvents$, (oldState, value) => {
      if (oldState.pageNumber > 1) {
        return {
          events: oldState.events.concat(value.events),
        };
      }
      return value;
    });
     
    // Derving state based on the event fired in facade for open event list
    this.connect(this.openEventsState.eventsClosed$, (oldState, value) => ({
      events: value.concat(oldState.events),
      count: oldState.count + value.length,
    }));
  }

  private fetchUserEvents(userId: string, pageNumber: number) {
    return this.http
      .get<IResponse<IUserEventsData>>(
        `vital/v1/users/${userId}/events`,
        {
          params: {
            status: EventStatus.close,
            pageNumber,
            recordPerPage: 25,
          },
        }
      );
  }
}

Если UserEventsComponent является вашим компонентом-контейнером для обоих компонентов представления (OpenEventsComponent, ClosedEventsComponent). Тогда эти состояния (OpenEventsState, ClosedEventsState) должны быть предоставлены на вашем компоненте-контейнере, а именно UserEventsComponentS

@Component({
  ...
  providers: [OpenEventsState, ClosedEventsState]
})
export class UserEventsComponent {
  ...
  ...
}
На этом я оставляю вас с задачей найти множество возможностей абстрагировать состояние вашего контейнерного компонента в этих элегантных фасадах.
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?

20.08.2023 18:21

Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в 2023-2024 годах? Или это полная лажа?".

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией

20.08.2023 17:46

В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.

Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox

19.08.2023 18:39

Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в частности, магию поплавков и гибкость flexbox.

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest

19.08.2023 17:22

В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для чтения благодаря своей простоте. Кроме того, мы всегда хотим проверить самые последние возможности в наших проектах!

Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️

18.08.2023 20:33

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL

14.08.2023 14:49

Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип предназначен для представления неделимого значения.