From c1ba86d71b32b7822489dea7aff171a40c464306 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 May 2018 10:33:03 -0700 Subject: [PATCH 1/5] merge fixed size test components into one --- .../scrolling/virtual-scroll-viewport.scss | 4 +- .../scrolling/virtual-scroll-viewport.spec.ts | 70 +++++++------------ .../scrolling/virtual-scroll-viewport.ts | 4 +- 3 files changed, 29 insertions(+), 49 deletions(-) diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.scss b/src/cdk-experimental/scrolling/virtual-scroll-viewport.scss index eef90817bbce..60f6c3b03edf 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.scss +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.scss @@ -14,11 +14,11 @@ cdk-virtual-scroll-viewport { will-change: contents, transform; } -.virtual-scroll-orientation-horizontal { +.cdk-virtual-scroll-orientation-horizontal { bottom: 0; } -.virtual-scroll-orientation-vertical { +.cdk-virtual-scroll-orientation-vertical { right: 0; } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index 27182f667b38..51ca32002403 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -272,25 +272,9 @@ describe('CdkVirtualScrollViewport', () => { expect(viewport.getOffsetToRenderedContentStart()) .toBe(testComponent.itemSize, 'should be scrolled to bottom of 5 item list'); })); - }); - - describe('with FixedSizeVirtualScrollStrategy and horizontal orientation', () => { - let fixture: ComponentFixture; - let testComponent: FixedHorizontalVirtualScroll; - let viewport: CdkVirtualScrollViewport; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ScrollingModule], - declarations: [FixedHorizontalVirtualScroll], - }).compileComponents(); - - fixture = TestBed.createComponent(FixedHorizontalVirtualScroll); - testComponent = fixture.componentInstance; - viewport = testComponent.viewport; - }); - - it('should update viewport as user scrolls right', fakeAsync(() => { + it('should update viewport as user scrolls right in horizontal mode', fakeAsync(() => { + testComponent.orientation = 'horizontal'; finishInit(fixture); const maxOffset = @@ -315,7 +299,8 @@ describe('CdkVirtualScrollViewport', () => { } })); - it('should update viewport as user scrolls left', fakeAsync(() => { + it('should update viewport as user scrolls left in horizontal mode', fakeAsync(() => { + testComponent.orientation = 'horizontal'; finishInit(fixture); const maxOffset = @@ -337,6 +322,7 @@ describe('CdkVirtualScrollViewport', () => { expect(viewport.measureRenderedContentSize()) .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, `rendered content size should match expected value at offset ${offset}`); + debugger; } })); }); @@ -370,48 +356,42 @@ function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { @Component({ template: ` + class="viewport" [itemSize]="itemSize" [bufferSize]="bufferSize" [orientation]="orientation" + [style.height.px]="viewportHeight" [style.width.px]="viewportWidth">
+ [style.height.px]="itemSize" [style.width.px]="itemSize"> {{i}} - {{item}}
`, - styles: [`.cdk-virtual-scroll-content-wrapper { display: flex; flex-direction: column; }`], + styles: [` + .cdk-virtual-scroll-content-wrapper { + display: flex; + flex-direction: column; + } + + .cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper { + flex-direction: row; + } + `], encapsulation: ViewEncapsulation.None, }) class FixedVirtualScroll { @ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport; @ViewChild(CdkVirtualForOf, {read: ViewContainerRef}) cdkForOfViewContainer: ViewContainerRef; + @Input() orientation = 'vertical'; @Input() viewportSize = 200; @Input() viewportCrossSize = 100; @Input() itemSize = 50; @Input() bufferSize = 0; @Input() items = Array(10).fill(0).map((_, i) => i); -} -@Component({ - template: ` - -
- {{i}} - {{item}} -
-
- `, - styles: [`.cdk-virtual-scroll-content-wrapper { display: flex; flex-direction: row; }`], - encapsulation: ViewEncapsulation.None, -}) -class FixedHorizontalVirtualScroll { - @ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport; + get viewportWidth() { + return this.orientation == 'horizontal' ? this.viewportSize : this.viewportCrossSize; + } - @Input() viewportSize = 200; - @Input() viewportCrossSize = 100; - @Input() itemSize = 50; - @Input() bufferSize = 0; - @Input() items = Array(10).fill(0).map((_, i) => i); + get viewportHeight() { + return this.orientation == 'horizontal' ? this.viewportCrossSize : this.viewportSize; + } } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index 6a9e3e15ced9..a0ad24085591 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -42,8 +42,8 @@ function rangesEqual(r1: ListRange, r2: ListRange): boolean { styleUrls: ['virtual-scroll-viewport.css'], host: { 'class': 'cdk-virtual-scroll-viewport', - '[class.virtual-scroll-orientation-horizontal]': 'orientation === "horizontal"', - '[class.virtual-scroll-orientation-vertical]': 'orientation === "vertical"', + '[class.cdk-virtual-scroll-orientation-horizontal]': 'orientation === "horizontal"', + '[class.cdk-virtual-scroll-orientation-vertical]': 'orientation === "vertical"', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, From 12e4c9e606792bfa66ced4a5c3156d649a06ea31 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 May 2018 14:35:04 -0700 Subject: [PATCH 2/5] add tests for cdkVirtualFor logic --- .../scrolling/virtual-scroll-viewport.spec.ts | 138 +++++++++++++++++- src/cdk/collections/array-data-source.ts | 6 +- 2 files changed, 138 insertions(+), 6 deletions(-) diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index 51ca32002403..4c19378363c5 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -1,7 +1,8 @@ +import {ArrayDataSource} from '@angular/cdk/collections'; import {dispatchFakeEvent} from '@angular/cdk/testing'; import {Component, Input, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; -import {animationFrameScheduler} from 'rxjs'; +import {animationFrameScheduler, Subject} from 'rxjs'; import {ScrollingModule} from './scrolling-module'; import {CdkVirtualForOf} from './virtual-for-of'; import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; @@ -322,9 +323,134 @@ describe('CdkVirtualScrollViewport', () => { expect(viewport.measureRenderedContentSize()) .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, `rendered content size should match expected value at offset ${offset}`); - debugger; } })); + + it('should work with an Observable', fakeAsync(() => { + const data = new Subject(); + testComponent.items = data as any; + finishInit(fixture); + + expect(viewport.getRenderedRange()) + .toEqual({start: 0, end: 0}, 'no items should be rendered'); + + data.next([1, 2, 3]); + fixture.detectChanges(); + + expect(viewport.getRenderedRange()) + .toEqual({start: 0, end: 3}, 'newly emitted items should be rendered'); + })); + + it('should work with a DataSource', fakeAsync(() => { + const data = new Subject(); + testComponent.items = new ArrayDataSource(data) as any; + finishInit(fixture); + flush(); + + expect(viewport.getRenderedRange()) + .toEqual({start: 0, end: 0}, 'no items should be rendered'); + + data.next([1, 2, 3]); + fixture.detectChanges(); + flush(); + + expect(viewport.getRenderedRange()) + .toEqual({start: 0, end: 3}, 'newly emitted items should be rendered'); + })); + + it('should trackBy value', fakeAsync(() => { + testComponent.items = []; + spyOn(testComponent.cdkForOfViewContainer, 'detach').and.callThrough(); + finishInit(fixture); + + testComponent.items = [0]; + fixture.detectChanges(); + + expect(testComponent.cdkForOfViewContainer.detach).not.toHaveBeenCalled(); + + testComponent.items = [1]; + fixture.detectChanges(); + + expect(testComponent.cdkForOfViewContainer.detach).toHaveBeenCalled(); + })); + + it('should trackBy index', fakeAsync(() => { + testComponent.trackBy = i => i; + testComponent.items = []; + spyOn(testComponent.cdkForOfViewContainer, 'detach').and.callThrough(); + finishInit(fixture); + + testComponent.items = [0]; + fixture.detectChanges(); + + expect(testComponent.cdkForOfViewContainer.detach).not.toHaveBeenCalled(); + + testComponent.items = [1]; + fixture.detectChanges(); + + expect(testComponent.cdkForOfViewContainer.detach).not.toHaveBeenCalled(); + })); + + it('should recycle views when template cache is large enough to accommodate', fakeAsync(() => { + testComponent.trackBy = i => i; + spyOn(testComponent.cdkForOfViewContainer, 'createEmbeddedView').and.callThrough(); + finishInit(fixture); + + // Should create views for the initial rendered items. + expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); + + (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + triggerScroll(viewport, 10); + fixture.detectChanges(); + + // As we first start to scroll we need to create one more item. This is because the first item + // is still partially on screen and therefore can't be removed yet. At the same time a new + // item is now partially on the screen at the bottom and so a new view is needed. + expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); + + (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = 10; offset <= maxOffset; offset += 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + } + + // As we scroll through the rest of the items, no new views should be created, our existing 5 + // can just be recycled as appropriate. + expect(testComponent.cdkForOfViewContainer.createEmbeddedView).not.toHaveBeenCalled(); + })); + + it('should not recycle views when template cache is full', fakeAsync(() => { + testComponent.trackBy = i => i; + testComponent.templateCacheSize = 0; + spyOn(testComponent.cdkForOfViewContainer, 'createEmbeddedView').and.callThrough(); + finishInit(fixture); + + // Should create views for the initial rendered items. + expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); + + (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + triggerScroll(viewport, 10); + fixture.detectChanges(); + + // As we first start to scroll we need to create one more item. This is because the first item + // is still partially on screen and therefore can't be removed yet. At the same time a new + // item is now partially on the screen at the bottom and so a new view is needed. + expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); + + (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = 10; offset <= maxOffset; offset += 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + } + + // Since our template cache size is 0, as we scroll through the rest of the items, we need to + // create a new view for each one. + expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(5); + })); }); }); @@ -356,9 +482,11 @@ function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { @Component({ template: ` -
{{i}} - {{item}}
@@ -386,6 +514,8 @@ class FixedVirtualScroll { @Input() itemSize = 50; @Input() bufferSize = 0; @Input() items = Array(10).fill(0).map((_, i) => i); + @Input() trackBy = (_, v) => v; + @Input() templateCacheSize = 20; get viewportWidth() { return this.orientation == 'horizontal' ? this.viewportSize : this.viewportCrossSize; diff --git a/src/cdk/collections/array-data-source.ts b/src/cdk/collections/array-data-source.ts index 5d950fbb2c76..50114292a6bb 100644 --- a/src/cdk/collections/array-data-source.ts +++ b/src/cdk/collections/array-data-source.ts @@ -11,8 +11,10 @@ import {DataSource} from './data-source'; /** DataSource wrapper for a native array. */ -export class ArrayDataSource implements DataSource { - constructor(private _data: T[] | Observable) {} +export class ArrayDataSource extends DataSource { + constructor(private _data: T[] | Observable) { + super(); + } connect(): Observable { return this._data instanceof Observable ? this._data : observableOf(this._data); From 270b7d24a0cd61e0ad0586a80052acd5d23c0523 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 May 2018 15:34:25 -0700 Subject: [PATCH 3/5] allow undefined to be explicitly passed as trackBy --- src/cdk-experimental/scrolling/virtual-for-of.ts | 11 ++++++----- .../scrolling/virtual-scroll-viewport.spec.ts | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/cdk-experimental/scrolling/virtual-for-of.ts b/src/cdk-experimental/scrolling/virtual-for-of.ts index 78524c225082..a83f59464efd 100644 --- a/src/cdk-experimental/scrolling/virtual-for-of.ts +++ b/src/cdk-experimental/scrolling/virtual-for-of.ts @@ -81,15 +81,16 @@ export class CdkVirtualForOf implements CollectionViewer, DoCheck, OnDestroy * the item and produces a value to be used as the item's identity when tracking changes. */ @Input() - get cdkVirtualForTrackBy(): TrackByFunction { + get cdkVirtualForTrackBy(): TrackByFunction | undefined { return this._cdkVirtualForTrackBy; } - set cdkVirtualForTrackBy(fn: TrackByFunction) { + set cdkVirtualForTrackBy(fn: TrackByFunction | undefined) { this._needsUpdate = true; - this._cdkVirtualForTrackBy = - (index, item) => fn(index + (this._renderedRange ? this._renderedRange.start : 0), item); + this._cdkVirtualForTrackBy = fn ? + (index, item) => fn(index + (this._renderedRange ? this._renderedRange.start : 0), item) : + undefined; } - private _cdkVirtualForTrackBy: TrackByFunction; + private _cdkVirtualForTrackBy: TrackByFunction | undefined; /** The template used to stamp out new elements. */ @Input() diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index 4c19378363c5..a709bcc30c58 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -358,7 +358,7 @@ describe('CdkVirtualScrollViewport', () => { .toEqual({start: 0, end: 3}, 'newly emitted items should be rendered'); })); - it('should trackBy value', fakeAsync(() => { + it('should trackBy value by default', fakeAsync(() => { testComponent.items = []; spyOn(testComponent.cdkForOfViewContainer, 'detach').and.callThrough(); finishInit(fixture); @@ -374,7 +374,7 @@ describe('CdkVirtualScrollViewport', () => { expect(testComponent.cdkForOfViewContainer.detach).toHaveBeenCalled(); })); - it('should trackBy index', fakeAsync(() => { + it('should trackBy index when specified', fakeAsync(() => { testComponent.trackBy = i => i; testComponent.items = []; spyOn(testComponent.cdkForOfViewContainer, 'detach').and.callThrough(); @@ -514,7 +514,7 @@ class FixedVirtualScroll { @Input() itemSize = 50; @Input() bufferSize = 0; @Input() items = Array(10).fill(0).map((_, i) => i); - @Input() trackBy = (_, v) => v; + @Input() trackBy; @Input() templateCacheSize = 20; get viewportWidth() { From 6e79ab121589c84643b0ee04bfde0c461745b367 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 May 2018 15:47:37 -0700 Subject: [PATCH 4/5] fix bazel build --- src/cdk-experimental/scrolling/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cdk-experimental/scrolling/BUILD.bazel b/src/cdk-experimental/scrolling/BUILD.bazel index f7dfdb24b012..2e7fcb97bd56 100644 --- a/src/cdk-experimental/scrolling/BUILD.bazel +++ b/src/cdk-experimental/scrolling/BUILD.bazel @@ -37,6 +37,7 @@ ts_library( srcs = glob(["**/*.spec.ts"]), deps = [ ":scrolling", + "//src/cdk/collections", "//src/cdk/testing", "@rxjs", ], From b90e01bbbd7bc221fde1ee2582f70a3a617ea692 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 11 May 2018 08:46:53 -0700 Subject: [PATCH 5/5] address comments --- .../scrolling/virtual-scroll-viewport.spec.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index a709bcc30c58..2f1363d8e018 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -360,55 +360,56 @@ describe('CdkVirtualScrollViewport', () => { it('should trackBy value by default', fakeAsync(() => { testComponent.items = []; - spyOn(testComponent.cdkForOfViewContainer, 'detach').and.callThrough(); + spyOn(testComponent.virtualForViewContainer, 'detach').and.callThrough(); finishInit(fixture); testComponent.items = [0]; fixture.detectChanges(); - expect(testComponent.cdkForOfViewContainer.detach).not.toHaveBeenCalled(); + expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); testComponent.items = [1]; fixture.detectChanges(); - expect(testComponent.cdkForOfViewContainer.detach).toHaveBeenCalled(); + expect(testComponent.virtualForViewContainer.detach).toHaveBeenCalled(); })); it('should trackBy index when specified', fakeAsync(() => { testComponent.trackBy = i => i; testComponent.items = []; - spyOn(testComponent.cdkForOfViewContainer, 'detach').and.callThrough(); + spyOn(testComponent.virtualForViewContainer, 'detach').and.callThrough(); finishInit(fixture); testComponent.items = [0]; fixture.detectChanges(); - expect(testComponent.cdkForOfViewContainer.detach).not.toHaveBeenCalled(); + expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); testComponent.items = [1]; fixture.detectChanges(); - expect(testComponent.cdkForOfViewContainer.detach).not.toHaveBeenCalled(); + expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); })); it('should recycle views when template cache is large enough to accommodate', fakeAsync(() => { testComponent.trackBy = i => i; - spyOn(testComponent.cdkForOfViewContainer, 'createEmbeddedView').and.callThrough(); + const spy = + spyOn(testComponent.virtualForViewContainer, 'createEmbeddedView').and.callThrough(); finishInit(fixture); // Should create views for the initial rendered items. - expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); + expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); - (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + spy.calls.reset(); triggerScroll(viewport, 10); fixture.detectChanges(); // As we first start to scroll we need to create one more item. This is because the first item // is still partially on screen and therefore can't be removed yet. At the same time a new // item is now partially on the screen at the bottom and so a new view is needed. - expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); + expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); - (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + spy.calls.reset(); const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = 10; offset <= maxOffset; offset += 10) { @@ -418,28 +419,29 @@ describe('CdkVirtualScrollViewport', () => { // As we scroll through the rest of the items, no new views should be created, our existing 5 // can just be recycled as appropriate. - expect(testComponent.cdkForOfViewContainer.createEmbeddedView).not.toHaveBeenCalled(); + expect(testComponent.virtualForViewContainer.createEmbeddedView).not.toHaveBeenCalled(); })); it('should not recycle views when template cache is full', fakeAsync(() => { testComponent.trackBy = i => i; testComponent.templateCacheSize = 0; - spyOn(testComponent.cdkForOfViewContainer, 'createEmbeddedView').and.callThrough(); + const spy = + spyOn(testComponent.virtualForViewContainer, 'createEmbeddedView').and.callThrough(); finishInit(fixture); // Should create views for the initial rendered items. - expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); + expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); - (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + spy.calls.reset(); triggerScroll(viewport, 10); fixture.detectChanges(); // As we first start to scroll we need to create one more item. This is because the first item // is still partially on screen and therefore can't be removed yet. At the same time a new // item is now partially on the screen at the bottom and so a new view is needed. - expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); + expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); - (testComponent.cdkForOfViewContainer.createEmbeddedView as any).calls.reset(); + spy.calls.reset(); const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = 10; offset <= maxOffset; offset += 10) { @@ -449,7 +451,7 @@ describe('CdkVirtualScrollViewport', () => { // Since our template cache size is 0, as we scroll through the rest of the items, we need to // create a new view for each one. - expect(testComponent.cdkForOfViewContainer.createEmbeddedView).toHaveBeenCalledTimes(5); + expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(5); })); }); }); @@ -506,7 +508,7 @@ function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { }) class FixedVirtualScroll { @ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport; - @ViewChild(CdkVirtualForOf, {read: ViewContainerRef}) cdkForOfViewContainer: ViewContainerRef; + @ViewChild(CdkVirtualForOf, {read: ViewContainerRef}) virtualForViewContainer: ViewContainerRef; @Input() orientation = 'vertical'; @Input() viewportSize = 200;