Другая ссылка на объект, переданная через ввод, не запускает чистый канал

#angular #pipe #angular-changedetection

#angular #канал #angular-changedetection

Вопрос:

TL: DR; — Чистый канал не запускается при поступлении новой ссылки, но должен. Есть идеи?

Мы не хотим использовать ngOnChanges или doCheck, потому что раньше мы заканчивали с красивым беспорядком кода sphagetti. Наш вариант использования достаточно прост, и мы просто хотим иметь возможность распространять данные на все дерево DOM.

OnPush не работает, уже пробовал


Мы проводим рефакторинг нашего приложения Angular, которое стало достаточно большим, чтобы иметь достаточно ошибок и проблем из-за дополнительной сложности событий (и событий websockets, обновляющих данные).

Поэтому мы решили создать службу передачи данных для каждой главной страницы приложения. Он сохранит состояние этой страницы и будет дескриптором перехода для обработки любых событий, которые может захотеть запустить любой подкомпонент, а также всех событий websocket, которые поступают из соединения

Проблема в том, что у нас много проблем с распространением данных. И что это устаревший код, поэтому мы не хотим изменять все подкомпоненты, поэтому нам нужно выполнить преобразование данных для ввода, требуемого подкомпонентами.

Мы уже знали о том, что Angular не может обнаружить изменения во внутренних элементах объекта, но это было исправлено достаточно быстро, (плохо) заставляя наш DataService создавать новые ссылки на объекты в каждом обновлении, поэтому у нас не было бы никаких проблем с тем, что Angular не обнаруживает их

 export interface ProjectData {
  list?: PageObject<Project>;
  active?: Project;
}

...
  private _state: ProjectData = {};
  private _stateSubject: BehaviorSubject<ProjectData> = new BehaviorSubject(this._state);
  public state$: Observable<ProjectData> = this._stateSubject.asObservable();
...

//We save the _state internally for checks on valid state updates (is the item active? is the item on the list?)
  requestList(options: QueryOptions) {
    this.projectService
      .list$(options)
      .pipe(
        map(page => {
          this._state.list = page;

          this._state = this.replicateState(this._state);

          return this._state;
        })
      )
      .subscribe(this._stateSubject);
  }

...
  // Use to force a new reference for the state object.
  // To be improved to actually make the logic work in an immutable way. For now, we want
  // to get this working, that's all.
  replicateState(oldState: ProjectData): ProjectData {
    return {
      list: oldState.list,
      active: oldState.active
    };
  }
  

Затем это состояние $ извлекается основным компонентом страницы (он же родительский компонент). Он будет передавать свои данные каждому подкомпоненту. В связи с этим вопросом у нас в настоящее время возникают проблемы с компонентом таблицы, в котором перечислены объекты, разбитые на страницы.

 <sv-table-v2
  [listPage]="(data$ | async).list"
  [headers]="headers$ | async"
  ...other inputs
></sv-table-v2>
  

И затем, у этого sv-table-v2 есть компонент, который отображает список. Хотя это из старой версии, и мы не хотели продолжать рефакторинг, поэтому мы хотели преобразовать нашу страницу в класс Rows, который компонент использует во входных данных

  <sv-table-list
    #rows
    [loaded]="listPage"
    [rows]="listPage.data | createRows: headers"
    ...other inputs
  ></sv-table-list>
  

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

Мы хотим иметь возможность распространять данные без необходимости копаться в onChanges или doCheck. Мы считаем их хакерскими, если вы не знаете достаточно хорошо, что с ними делать, не имеете сложного случая для управления или проблем с производительностью, которых у нас нет ни для чего, кроме передачи входных данных T_T . Мы также получили целую загрузку кода sphaguetti раньше, поэтому мы пытаемся избежать этого, даже если многие ответы пытаются взломать такие вещи с помощью этих методов жизненного цикла

На самом деле, мы только хотим иметь возможность распространять изменения из службы данных на все дерево DOM. Вот и все.

Мы уже пробовали стратегию OnPush вместо конвейера, но она тоже не работает. И, насколько я прочитал, нам все равно нужно, чтобы Angular заметил, что ссылка изменилась.

Также: все это РАБОТАЕТ, если мы не используем канал. Данные не будут отображаться в таблице (потому что это неправильный тип), но список обновит его размер в соответствии с размером страницы, который мы запрашиваем, поэтому мы знаем, что данные могут распространяться правильно. Вот почему канал является нашим главным подозреваемым, это единственное изменение.

Редактировать:

Код для канала, который я забыл

 import { Pipe, PipeTransform } from '@angular/core';
import { Resource } from 'src/app/core/models/resource';
import { ColumnHeader, Table } from 'src/app/core/models/table-data';

@Pipe({
  name: 'createRows'
})
export class CreateRowsPipe implements PipeTransform {
  transform(data: Resource[], headers: ColumnHeader[]): any {
    return new Table(data, headers).rows;
  }
}
  

Комментарии:

1. Я не уверен, что без надлежащей отладки вашего кода, но вы могли бы использовать оператор распространения, чтобы заметить изменение ссылки, следовательно, попробуйте return { ...oldState, list: oldState.list, active: oldState.active } свой replicateState метод.

Ответ №1:

Итак, мы нашли решение еще в прошлом, но я чувствую его хакерским, особенно потому, что эта практика была заимствована из SO, как правильный способ заставить субъекта подписаться на observable, чтобы получить от них все события.

Итак:

   requestList(options: QueryOptions) {
    this.projectService
      .list$(options)
      .pipe(
        map(page => {
          this._state.list = page;

          this._state = this.replicateState(this._state);

          return this._state;
        })
      )
      .subscribe(this._stateSubject);
  }
  

Этот метод и каналы будут вызываться каждый раз при вызове метода и выполнении HTTP-запроса, но подписка не будет отправлять новое событие (результат HTTP-запроса) в this._stateSubject после неизвестного события. Это похоже на это._stateSubject просто перестает отправлять данные.

Но если у вас есть базовый способ отправки его вручную (что я делал до того, как получил ответ так давно)

   requestList(options: QueryOptions) {
    this.projectService
      .list$(options)
      .pipe(
        map(page => {
          this._state.list = page;

          this._state = this.replicateState(this._state);

          return this._state;
        })
      )
      .subscribe(state => this._stateSubject.next(state));
  }
  

Тогда объект никогда не перестанет работать и будет отправлять любые новые данные http-запросов, которые выполняются при вызове этой функции.

Теперь, когда я пишу это, я думаю, что проблема может заключаться в том, что подписка на подобную тему, у которой есть весь наблюдаемый интерфейс, может прекратиться, потому что HTTP-запрос отправляет сигнал завершения, поэтому тема также завершается через неизвестное время. Но это то, что я понял, когда писал это, и я не уверен, что это основная проблема.

TLDR: просто отправьте его с помощью next, если у вас возникли проблемы. Я не уверен, в чем заключается основная проблема.