Angular - динамический компонент отображает неверные данные

Решено

Как упоминается ниже Аароном, подход «Другой вариант» работает без проблем и кажется намного чище. Я оставлю обновления и все остальное на случай, если у кого-то в будущем возникнет аналогичная проблема, и обходные пути как-то пригодятся.


У меня странная проблема при попытке визуализировать динамические компоненты внутри таблицы материалов Angular с разбивкой на страницы и сортировкой.

Вот Stackblitz с воспроизведенной проблемой. Судя по тому, что я исследовал, похоже, что это какая-то проблема с обнаружением изменений, хотя я могу ошибаться в этом, поскольку понятия не имею, почему это происходит. Если у кого-то есть идеи, как решить эту проблему, или если я делаю что-то явно не так, мы будем очень благодарны за отзывы и помощь.

Текущее поведение:

Я использую @ViewChildren и ComponentFactoryResolver внутри таблицы материального дизайна для динамического рендеринга компонентов с несколькими страницами данных. Все выглядит нормально, однако при использовании matSort для сортировки данных некоторые строки с динамическими компонентами показывают неправильные значения данных, тогда как остальная часть строки (нединамическая) имеет правильные. Смотрите скриншоты ниже:

Перед сортировкой:

2

После сортировки:

3

консольный журнал экземпляров компонентов (обратите внимание, что передано правильное поле статуса, но отображается неправильное).

3

Ожидаемое поведение:

При сортировке данные и визуализированные компоненты отображаются правильно. Должно соответствовать полю dataSource.data.

Минимальное воспроизведение проблемы с инструкциями:

Посетите Stackblitz и запустите приложение. Щелкните заголовок сортировки для статуса заказа. Обратите внимание, как первая запись в таблице говорит «Доставлено», а фактический источник данных - как «Отменено». Также не то, чтобы остальная часть строки была точной - просто динамически отображаемый компонент неверен.

Что я пробовал:

  • Пытался установить статус с помощью get () и set () вместо @Input () и зарегистрировать ngOnChage, где я вызываю changeDetectorRef из самого компонента, но проблема не исчезла.

  • Пробовал менять ChangeDetectionStrategy - ни один ничего не сделал.

  • Также пробовал решения в приведенном ниже разделе

Возможные похожие сообщения, которые я просматривал, но не упоминал о решениях

Кажется, это та же проблема, но ответа на нее так и не было. Я надеюсь, что предоставление Stackblitz поможет получить тот же ответ. Судя по плакату, это какая-то проблема с обнаружением изменений.

При загрузке одного и того же компонента с разными данными, старые данные все еще отображаются - Angular 5

Пытался явно вызвать методы detectChanges () и markForCheck () chanceDetectionRef для componentRefs, родительского компонента, фактического компонента, который динамически создается безуспешно.

Проблема обнаружения изменения загрузчика динамических компонентов Angular

Обнаружение изменений не работает при создании компонента через ComponentFactoryResolver

Возможное решение / более странное поведение:

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

Обновление 1

В соответствии с дополнительными сведениями Аарона ниже, я добавил Обновлен Stackblitz с гораздо более упрощенным примером. При более внимательном рассмотрении нумерация страниц не играет роли в странном поведении. Я могу воспроизвести проблему всего с двумя записями таблицы и сортировкой (весь код разбивки на страницы удален). Я изменил входящие данные, чтобы они соответствовали строкам, чтобы было понятнее, какие строки откуда берутся.

Вот комбинированный снимок экрана до и после сортировки с 2, 3 и 4 записями:

введите описание изображения здесь

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

Обновление 2

Один обходной путь, который я смог найти, - это установить мой dataSource в пустой массив после того, как я сохранил его копию с splice() в onSortChange() прямо перед выполнением моей логики фильтрации, а затем вернул ее к отфильтрованному массиву. Я предполагаю, что это вызывает какое-то обновление в источнике данных / таблице материалов, которое вызывает повторную визуализацию или обновление. Я буду продолжать пытаться выяснить, есть ли лучший подход к этому, поскольку это кажется странным, и в моем исходном приложении у меня есть filterPredicate, который также вызывает ту же проблему при фильтрации / сбросе фильтра.

Обновление 3

Аааааи, наконец, после еще нескольких исследований по этому поводу, я пришел к хотя бы некоторому решению, которое имеет смысл. После того, что я сказал в обновлении №2, я нашел этот пост:

https://github.com/angular/components/issues/15972#issuecomment-490235603

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




Ответы (1)


Я не на 100% то, что происходит, но немного поигрался с этим, я не думаю, что проблема заключается в обнаружении изменений. Я начал с того, что комментировал все после

cellTemplateRef.clear(); в createDynamicComponents() в dynamic-table.components.ts

Сделав это, мы определенно увидим, что все в этой колонке исчезнет.

Затем, используя другой подход, закомментировав только cellTemplateRef.clear(), вы увидите, как ваши новые компоненты вставляются поверх старых.

введите описание изображения здесь

Следующим шагом было удаление старого компонента после добавления нового ...

После цикла Object.keys ....:

    if (cellTemplateRef.length > 1) {
      cellTemplateRef.remove(1);
    } 

Понятия не имею, почему clear() не выполняет свою работу, но удаление очистки и добавление прямого удаления экземпляра компонента кажется обходным путем.

Заметив, что это решение даже не сортируется должным образом, вот еще некоторое представление о том, что происходит. Добавьте индекс @Input () в свой StatusIconComponent и отобразите его в своем компоненте. Тогда в вашем цикле ...

cellComponentRef.instance["index"] = i;

После сортировки перед сортировкой вы увидите что-то вроде этого:

введите описание изображения здесь

А потом после сортировки ...

введите описание изображения здесь

Обратите внимание, что строки отображаются не в ожидаемом порядке.

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

Это все еще не отвечает на ваш вопрос, но, надеюсь, дает больше информации.

Другой вариант

Я подозреваю, что порядок ViewChildren в ng-шаблоне в ячейках данных не такой, как вы могли бы надеяться. Так что вместо того, чтобы делать шаблоны в таблице, как насчет создания DynamicCellComponent, в котором вы выполняете всю магию фабрики компонентов.

Итак, ваш шаблон таблицы будет выглядеть примерно так ...

<td mat-cell *matCellDef="let data">
  <!-- moving your ngIfs to the component -->

  <app-dynamic-cell [columnDef]="column" [data]="data"></app-dynamic-cell>
</td>

Затем создайте компонент, подобный этому (в основном переместил большую часть вашего кода из таблицы).

@Component({
  selector: "app-dynamic-cell",
  templateUrl: "./dynamic-cell.component.html",
  styleUrls: ["./dynamic-cell.component.css"]
})
export class DynamicCellComponent implements AfterViewInit {
  @Input() columnDef: DynamicTableColumn;
  @Input() data: any;

  @ViewChild("container", { read: ViewContainerRef })
  private container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver, private cdr: ChangeDetectorRef) {}

  isValueDynamicComponent(value: any) {
    return !(typeof value === "function");
  }

  ngAfterViewInit() {
    console.log(this.columnDef, this.data);
    const viewContainerRef = this.container;
    if (viewContainerRef) {
      viewContainerRef.clear();

      const { type: componentType, inputs: inputsFn } = (this.columnDef
        .value as any) as DynamicComponent;

      const componentInputs = inputsFn(this.data);

      // create the component instance and store a reference to it
      const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
        componentType
      );

      const cellComponentRef = viewContainerRef.createComponent<
        typeof componentType
      >(componentFactory);

      // populate any properties of the component
      Object.keys(componentInputs).forEach(key => {
        cellComponentRef.instance[key] = componentInputs[key];
      });

      this.cdr.detectChanges();
    }
  }
}

И затем компонент html сделает то, что вы делали в определении ячейки раньше.

<ng-container *ngIf="!isValueDynamicComponent(columnDef.value)">{{ columnDef.value(data)}}</ng-container>
<ng-container *ngIf="isValueDynamicComponent(columnDef.value)"#container></ng-container>

Таким образом, вы не будете беспокоиться о внутренней работе таблицы данных, о порядке рендеринга и т. Д.

person Aaron Hinni    schedule 17.03.2021
comment
Хм, я понимаю, о чем вы говорите. remove(1) определенно изменяет первые несколько динамических компонентов, но некоторые из них все еще отключены (например, Отправлено # 11) на самом деле Доставлено в таком виде. Я исследую remove(1) немного дальше, чтобы увидеть, смогу ли я заставить его вести себя нормально - person iEquivalent; 17.03.2021
comment
Вероятно, стоит покопаться в исходном коде mat-table и / или cdk table, чтобы понять, как они отрисовывают вещи. В какой-то момент я создал собственный источник данных, и было очень полезно увидеть, что происходит за кулисами. - person Aaron Hinni; 17.03.2021
comment
А ваш комментарий по поводу Shipped # 11 интересен. Обратите внимание, что №11 не отображался в первый раз (до нажатия кнопки сортировки), так как он был на 2-й странице. Другая мысль, которая у меня возникла, заключалась в том, что вместо удаления / создания компонентов вам может потребоваться get и существующий компонент, если он уже существует, и просто обновить его ввод и создать только в том случае, если его нет. - person Aaron Hinni; 17.03.2021
comment
Да, это был мой следующий шаг, если я не мог понять проблему. Кажется, это происходит только тогда, когда источник данных занимает несколько страниц. Вроде бы все хорошо, когда все хотя бы на одной странице. Я опробую подход к обновлению сегодня же и посмотрю, сработает ли это. - person iEquivalent; 17.03.2021
comment
Я подбросил еще кое-что, что касается порядка рендеринга вещей. - person Aaron Hinni; 17.03.2021
comment
Интересный. Я добавил небольшое обновление выше с упрощенным Stackblitz на основе этого. Мне интересно, не запускает ли matSort повторный рендеринг этих компонентов. Хотя я не уверен, как явно перерисовать их, так как при вызове changeDetectorRef для шаблонов, когда я пытался, похоже, этого не произошло. Интересно, что когда у меня был пагинатор, переключение между страницами вперед и назад приводило к отрисовке правильных компонентов. - person iEquivalent; 18.03.2021
comment
Также отредактировал некоторую информацию о возможном обходном пути, который я нашел - person iEquivalent; 18.03.2021
comment
Добавлен еще один вариант с использованием DynamicCellComponent. - person Aaron Hinni; 18.03.2021
comment
Потрясающе - это намного лучше, чем обходной путь сброса ссылок на источники данных, который я придумал. В итоге я выбрал дополнительный компонент, и все работает нормально и, как и ожидалось. Спасибо за помощь! - person iEquivalent; 18.03.2021
comment
Не за что. Я тоже многому научился. - person Aaron Hinni; 18.03.2021