ngFor зависит от другого ngFor для завершения рендеринга - получение ExpressionChangedAfterItHasBeenCheckedError [Angular 2]

У меня есть два списка компонентов, которые используют данные, предоставленные двумя разными сервисами:

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

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

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

Я попытался использовать ChangeDetectorRef.detectChanges () или прослушать изменения, испускаемые QueryList, содержащим компоненты первого списка, но я все еще получаю ExpressionChangedAfterItHasBeenCheckedError.

Реальный сценарий немного сложнее, но вот упрощенная версия того, как выглядит код:

https://embed.plnkr.co/sr9k0wLQtyWSATiZuqaK/

Заранее спасибо, это мой первый вопрос по stackoverflow :)


person ady_mc    schedule 08.10.2017    source источник
comment
вы можете найти эту статью полезной Все, что вы нужно знать об ExpressionChangedAfterItHasBeenCheckedError ошибке   -  person Max Koretskyi    schedule 08.10.2017


Ответы (2)


Я бы не стал использовать методы расчета высоты в шаблоне. Вместо этого я бы подготовил данные для просмотра:

<second-cmp 
  *ngFor="let cmp of generatedData" 
 [model]="cmp" 
 [height]="cmp.height"> <------------ already calculated value

Тогда я бы подписался на QueryList.changes, чтобы отслеживать изменения сгенерированных элементов и вычислять height там:

constructor(
  ...,
  private cdRef: ChangeDetectorRef) {}

ngAfterViewInit() {
    this.components.changes.subscribe(components => {
      const renderedCmps =  components.toArray();
      this.generatedData.forEach((x, index) => {
        switch (index) {
          case 0: // first
            x.height = renderedCmps[0].height / 2;
            break;
          case this.modelData.length: // last
            x.height = (renderedCmps[renderedCmps.length - 1].height / 2);
            break;
          default: // in-between
            x.height = (renderedCmps[index - 1].height + renderedCmps[index].height) / 2
        }
      });
      this.cdRef.detectChanges();
    });
}

+ 'px' является избыточным, потому что мы можем указать его в привязке стиля:

[style.height.px]="height"

Пример Plunker

person yurzui    schedule 08.10.2017

Отличный ответ @yurzui, работает как шарм!

Установка свойства высоты для данных модели также избавляет от необходимости связывать его отдельно, поскольку я уже передаю ссылку на всю модель компоненту в файле * ngFor.

<second-cmp 
  *ngFor="let cmp of generatedData" 
 [model]="cmp"> <!-- the height doesn't need to be binded separately anymore -->

@Component({
selector: 'second-cmp',
template: `
  <li>
    <div class="item" [style.height.px]="model.height">Calculated height</div>
  </li>`
)}
export class Second {
  @Input('model') model;
  // @Input('height') height; <-- removed
}

Final solution

person ady_mc    schedule 08.10.2017
comment
Хорошее улучшение ) - person yurzui; 08.10.2017