简体   繁体   中英

ngFor depends on another ngFor to finish rendering - getting ExpressionChangedAfterItHasBeenCheckedError [Angular 2]

I have two lists of components that use data provided by two different services:

  • the first list contains some components with random heights (I don't know their heights until they are rendered)
  • the components in the second list must have their heights calculated based on the heights of the ones in the first list.

Both lists are generated by the same component using two *ngFor loops, and the services data is changed from another child component.

The problem is that when the model data changes, both ngFor loops try to update the template, but the second one fails because it is dependent upon the first ngFor which is not yet ready.

I did try to use ChangeDetectorRef.detectChanges() or to listen for the changes emitted by the QueryList containing the first list's components, but I'm still getting ExpressionChangedAfterItHasBeenCheckedError.

The real scenario is a bit more complicated, but here's a simplified version of what the code looks like:

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

Thanks in advance, this is my first question on stackoverflow :)

I would avoid using methods that calculate height within template. Instead i would prepare data for view:

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

Then i would subscribe to QueryList.changes to track changes of generated elements and calculate height there:

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' is redundant because we can specify it within style binding:

[style.height.px]="height"

Plunker Example

Excellent response @yurzui , works like a charm!

Setting the height property on the model data also removes the necessity of binding it separately since I'm already passing the reference of the entire model to the component in the *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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM