Obtaining a static component reference within a cdk-virtual-scroller? (References are recycled)

回眸只為那壹抹淺笑 提交于 2019-12-24 04:06:08

问题


We recently transitioned our scrollable lists to CDK Virtual Scroller. (Angular 7.2.12 with angular/cdk 7.3.7)

In short, it seems that the VirtualScrollViewport is recycling component instances, not just the template as the documentation suggests.

Live MCVE on StackBlitz (updated to reflect EDIT 1).

EDIT 1

A colleague reminded me that we're now using named references instead of ViewChildren(), like so:

HelloComponent (inside the *cdkVirtualFor):

@Component({
  selector: 'hello',
  template: `<h1 [class.active]="active">Data Item {{item}} !</h1>`,
  styles: [`.active {background-color: red; color: white}`]
})
export class HelloComponent  {
  @Input() item: any;
  active: boolean = false;
  toggle = () => this.active = !this.active;
}

And implementing it in the App like:

<cdk-virtual-scroll-viewport itemSize="75">
  <ng-container *cdkVirtualFor="let item of data" templateCacheSize=0>
    <hello #hi [item]="item" (click)="clickByReference(hi)"></hello>
  </ng-container>
</cdk-virtual-scroll-viewport>

// Non-essentials hidden, see StackBlitz
export class AppComponent  {
  data = Array.from(Array(100).keys())
  clickByReference = (element: any): void => element.toggle();
}

It will change the background colour of the clicked element to red, but when scrolling, others (presumably those that match some cached index?) will already be red! Activating one of those will clear the original as well.

The source suggests that templateCacheSize might help, but it doesn't.

Original

The scrollable area contains components which we get a reference to with a @ViewChildren() and QueryList and we track which one we are acting on using an index in the *ngFor (now *cdkVirtualFor), like so:

<cdk-virtual-scroll-viewport itemSize="75">
  <ng-container *cdkVirtualFor="let item of data; let i = index">
    <hello  #hi
            [item]="item"
            (click)="click(i)"></hello>
  </ng-container>
</cdk-virtual-scroll-viewport>

Then, from the page, we communicate with the component in the list:

export class AppComponent  {
  @ViewChildren('hi') hiRefs: QueryList<HelloComponent>;
  data = Array.from(Array(100).keys())

  click = (i: number) => this.hiRefs["_results"][i].say(`Hello as ${i}`);
}

Of course, now that the template is rendered in a virtual scroll container, only the first n are rendered into the DOM. So if you scroll down the list beyond what is initially loaded, hiRefs does not contain a reference to the item with the corresponding index, throwing a ReferenceError for the provided ["_results"][i].

I experimented with trackBy but didn't get anything fruitful.

EDIT: A colleague has also attempted to pass a named reference, which curiously has the same problem.

Updating the HelloComponent to

@Component({
  selector: 'hello',
  template: `<h1 [class.active]="active">Data Item {{item}} !</h1>`,
  styles: [`.active {background-color: red}`]
})
export class HelloComponent  {
  @Input() item: any;
  active: boolean;

  say = (something: any) => this.active = !this.active;
}

And implementing it in the App like:

<hello #hi [item]="item" (click)="clickByReference(hi)"></hello>

It will change the background colour of the clicked element to red, but when scrolling, others (presumably those that match the same index) will already be red, despite not using the @ViewChildren() QueryList at all!

It seems that the CDK is recycling component instance references?

I updated the StackBlitz with the method clickByReference(), and renamed the one above to clickByIndex().

How can I correctly get a reference to the component in the list in order to call methods on it?


回答1:


By default, CdkVirtualForOf caches 20 ViewRefs to components that are no longer rendered into the DOM to improve scrolling performance.

While these update to show new bound @Input()s, they do not update their internal state, so previously-cached copies are re-used as a result.

It seems the only solution is to set templateCacheSize: 0:

<ng-container *cdkVirtualFor="let item of data; templateCacheSize: 0">

That way the components are destroyed once they're no longer visible, and state is lost.

Further reading https://github.com/angular/material2/issues/15838



来源:https://stackoverflow.com/questions/55696084/obtaining-a-static-component-reference-within-a-cdk-virtual-scroller-reference

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!