Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(scrolling): virtual scroll throw off if directive injects ViewContainerRef #16137

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions src/cdk/scrolling/virtual-for-of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,20 +357,20 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy

/** Creates a new embedded view and moves it to the given index */
private _createEmbeddedViewAt(index: number): EmbeddedViewRef<CdkVirtualForOfContext<T>> {
const view = this._viewContainerRef.createEmbeddedView(this._template, {
$implicit: null!,
cdkVirtualForOf: this._cdkVirtualForOf,
index: -1,
count: -1,
first: false,
last: false,
odd: false,
even: false
});
if (index < this._viewContainerRef.length) {
this._viewContainerRef.move(view, index);
}
return view;
// Note that it's important that we insert the item directly at the proper index,
// rather than inserting it and the moving it in place, because if there's a directive
// on the same node that injects the `ViewContainerRef`, Angular will insert another
// comment node which can throw off the move when it's being repeated for all items.
return this._viewContainerRef.createEmbeddedView(this._template, {
$implicit: null!,
cdkVirtualForOf: this._cdkVirtualForOf,
index: -1,
count: -1,
first: false,
last: false,
odd: false,
even: false
}, index);
}

/** Inserts a recycled view from the cache at the given index. */
Expand Down
73 changes: 72 additions & 1 deletion src/cdk/scrolling/virtual-scroll-viewport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import {
NgZone,
TrackByFunction,
ViewChild,
ViewEncapsulation
ViewEncapsulation,
Directive,
ViewContainerRef
} from '@angular/core';
import {async, ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing';
import {animationFrameScheduler, Subject} from 'rxjs';
Expand Down Expand Up @@ -797,6 +799,36 @@ describe('CdkVirtualScrollViewport', () => {
'Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.');
}));
});

describe('with item that injects ViewContainerRef', () => {
let fixture: ComponentFixture<VirtualScrollWithItemInjectingViewContainer>;
let testComponent: VirtualScrollWithItemInjectingViewContainer;
let viewport: CdkVirtualScrollViewport;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ScrollingModule],
declarations: [VirtualScrollWithItemInjectingViewContainer, InjectsViewContainer],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(VirtualScrollWithItemInjectingViewContainer);
testComponent = fixture.componentInstance;
viewport = testComponent.viewport;
});

it('should render the values in the correct sequence when an item is ' +
'injecting ViewContainerRef', fakeAsync(() => {
finishInit(fixture);

const contentWrapper =
viewport.elementRef.nativeElement.querySelector('.cdk-virtual-scroll-content-wrapper')!;

expect(Array.from(contentWrapper.children).map(child => child.textContent!.trim()))
.toEqual(['0', '1', '2', '3', '4', '5', '6', '7']);
}));
});
});


Expand Down Expand Up @@ -938,3 +970,42 @@ class FixedSizeVirtualScrollWithRtlDirection {
class VirtualScrollWithNoStrategy {
items = [];
}

@Directive({
selector: '[injects-view-container]'
})
class InjectsViewContainer {
constructor(public viewContainerRef: ViewContainerRef) {
}
}

@Component({
template: `
<cdk-virtual-scroll-viewport itemSize="50">
<div injects-view-container class="item" *cdkVirtualFor="let item of items">{{item}}</div>
</cdk-virtual-scroll-viewport>
`,
styles: [`
.cdk-virtual-scroll-content-wrapper {
display: flex;
flex-direction: column;
}

.cdk-virtual-scroll-viewport {
width: 200px;
height: 200px;
}

.item {
width: 100%;
height: 50px;
}
`],
encapsulation: ViewEncapsulation.None
})
class VirtualScrollWithItemInjectingViewContainer {
@ViewChild(CdkVirtualScrollViewport, {static: true}) viewport: CdkVirtualScrollViewport;
itemSize = 50;
items = Array(20000).fill(0).map((_, i) => i);
}