From 46307f7e9504e9f74ac9f9343b047135cf136b98 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 27 Jul 2018 10:56:10 -0700 Subject: [PATCH 1/3] feat(virtual-scroll): move from cdk-experimental to cdk --- src/cdk-experimental/scrolling/BUILD.bazel | 13 +- .../scrolling/auto-size-virtual-scroll.ts | 7 +- src/cdk-experimental/scrolling/public-api.ts | 4 - .../scrolling/scrolling-module.ts | 17 +- src/cdk-experimental/scrolling/scrolling.md | 32 + .../scrolling/tsconfig-build.json | 3 +- .../scrolling/virtual-scroll-viewport.spec.ts | 619 +---------------- .../scrolling/virtual-scroll.md | 162 ----- src/cdk/overlay/overlay-module.ts | 6 +- .../connected-position-strategy.spec.ts | 4 +- ...exible-connected-position-strategy.spec.ts | 4 +- src/cdk/scrolling/BUILD.bazel | 8 + .../scrolling/fixed-size-virtual-scroll.ts | 0 src/cdk/scrolling/index.ts | 6 + src/cdk/scrolling/public-api.ts | 6 +- src/cdk/scrolling/scroll-dispatcher.spec.ts | 4 +- src/cdk/scrolling/scrolling-module.ts | 19 +- src/cdk/scrolling/scrolling.md | 130 ++++ src/cdk/scrolling/tsconfig-build.json | 3 +- .../scrolling/typings.d.ts | 0 src/cdk/scrolling/viewport-ruler.spec.ts | 4 +- src/cdk/scrolling/viewport.md | 3 - .../scrolling/virtual-for-of.ts | 0 .../scrolling/virtual-scroll-strategy.ts | 0 .../scrolling/virtual-scroll-viewport.html | 0 .../scrolling/virtual-scroll-viewport.scss | 0 .../scrolling/virtual-scroll-viewport.spec.ts | 631 ++++++++++++++++++ .../scrolling/virtual-scroll-viewport.ts | 0 src/demo-app/demo-material-module.ts | 6 +- src/lib/sidenav/sidenav-module.ts | 4 +- src/lib/tabs/tab-header.spec.ts | 4 +- src/material-examples/material-module.ts | 6 +- 32 files changed, 866 insertions(+), 839 deletions(-) create mode 100644 src/cdk-experimental/scrolling/scrolling.md delete mode 100644 src/cdk-experimental/scrolling/virtual-scroll.md rename src/{cdk-experimental => cdk}/scrolling/fixed-size-virtual-scroll.ts (100%) rename src/{cdk-experimental => cdk}/scrolling/typings.d.ts (100%) delete mode 100644 src/cdk/scrolling/viewport.md rename src/{cdk-experimental => cdk}/scrolling/virtual-for-of.ts (100%) rename src/{cdk-experimental => cdk}/scrolling/virtual-scroll-strategy.ts (100%) rename src/{cdk-experimental => cdk}/scrolling/virtual-scroll-viewport.html (100%) rename src/{cdk-experimental => cdk}/scrolling/virtual-scroll-viewport.scss (100%) create mode 100644 src/cdk/scrolling/virtual-scroll-viewport.spec.ts rename src/{cdk-experimental => cdk}/scrolling/virtual-scroll-viewport.ts (100%) diff --git a/src/cdk-experimental/scrolling/BUILD.bazel b/src/cdk-experimental/scrolling/BUILD.bazel index 054f9f5aae51..43bb6c21bde9 100644 --- a/src/cdk-experimental/scrolling/BUILD.bazel +++ b/src/cdk-experimental/scrolling/BUILD.bazel @@ -8,29 +8,22 @@ ng_module( name = "scrolling", srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]), module_name = "@angular/cdk-experimental/scrolling", - assets = [":virtual-scroll-viewport.css"] + glob(["**/*.html"]), deps = [ "//src/cdk/coercion", "//src/cdk/collections", - "//src/cdk/platform", + "//src/cdk/scrolling", "@rxjs", ], tsconfig = "//src/cdk-experimental:tsconfig-build.json", ) -sass_binary( - name = "virtual_scroll_viewport_scss", - src = "virtual-scroll-viewport.scss", -) - - ts_library( name = "scrolling_test_sources", testonly = 1, srcs = glob(["**/*.spec.ts"]), deps = [ ":scrolling", - "//src/cdk/collections", + "//src/cdk/scrolling", "//src/cdk/testing", "@rxjs", ], @@ -42,8 +35,6 @@ ts_web_test( bootstrap = [ "//:web_test_bootstrap_scripts", ], - tags = ["manual"], - # Do not sort deps = [ "//:tslib_bundle", diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index b42c8062dae5..5dfa2c16ea9d 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -8,10 +8,13 @@ import {coerceNumberProperty} from '@angular/cdk/coercion'; import {ListRange} from '@angular/cdk/collections'; +import { + CdkVirtualScrollViewport, + VIRTUAL_SCROLL_STRATEGY, + VirtualScrollStrategy +} from '@angular/cdk/scrolling'; import {Directive, forwardRef, Input, OnChanges} from '@angular/core'; import {Observable} from 'rxjs'; -import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy'; -import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; /** diff --git a/src/cdk-experimental/scrolling/public-api.ts b/src/cdk-experimental/scrolling/public-api.ts index 176164b1ed6c..a2b70ac841e5 100644 --- a/src/cdk-experimental/scrolling/public-api.ts +++ b/src/cdk-experimental/scrolling/public-api.ts @@ -7,8 +7,4 @@ */ export * from './auto-size-virtual-scroll'; -export * from './fixed-size-virtual-scroll'; export * from './scrolling-module'; -export * from './virtual-for-of'; -export * from './virtual-scroll-strategy'; -export * from './virtual-scroll-viewport'; diff --git a/src/cdk-experimental/scrolling/scrolling-module.ts b/src/cdk-experimental/scrolling/scrolling-module.ts index cccaaacb3eb4..1726a25b7f10 100644 --- a/src/cdk-experimental/scrolling/scrolling-module.ts +++ b/src/cdk-experimental/scrolling/scrolling-module.ts @@ -8,23 +8,10 @@ import {NgModule} from '@angular/core'; import {CdkAutoSizeVirtualScroll} from './auto-size-virtual-scroll'; -import {CdkFixedSizeVirtualScroll} from './fixed-size-virtual-scroll'; -import {CdkVirtualForOf} from './virtual-for-of'; -import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; @NgModule({ - exports: [ - CdkAutoSizeVirtualScroll, - CdkFixedSizeVirtualScroll, - CdkVirtualForOf, - CdkVirtualScrollViewport, - ], - declarations: [ - CdkAutoSizeVirtualScroll, - CdkFixedSizeVirtualScroll, - CdkVirtualForOf, - CdkVirtualScrollViewport, - ], + exports: [CdkAutoSizeVirtualScroll], + declarations: [CdkAutoSizeVirtualScroll], }) export class ScrollingModule {} diff --git a/src/cdk-experimental/scrolling/scrolling.md b/src/cdk-experimental/scrolling/scrolling.md new file mode 100644 index 000000000000..75c6a168b840 --- /dev/null +++ b/src/cdk-experimental/scrolling/scrolling.md @@ -0,0 +1,32 @@ +**Warning: this component is still experimental. It may have bugs and the API may change at any +time** + +### Scrolling over items with different sizes +When the items have different or unknown sizes, you can use the `AutoSizeVirtualScrollStrategy`. +This can be added to your viewport by using the `autosize` directive. + +```html + + ... + +``` + +The `autosize` strategy is configured through two inputs: `minBufferPx` and `addBufferPx`. + +**`minBufferPx`** determines the minimum space outside virtual scrolling viewport that will be +filled with content. Increasing this will increase the amount of content a user will see before more +content must be rendered. However, too large a value will cause more content to be rendered than is +necessary. + +**`addBufferPx`** determines the amount of content that will be added incrementally as the viewport +is scrolled. This should be greater than the size of `minBufferPx` so that one "render" is needed at +a time. + +```html + + ... + +``` + +Because the auto size strategy needs to measure the size of the elements, its performance may not +be as good as the fixed size strategy. diff --git a/src/cdk-experimental/scrolling/tsconfig-build.json b/src/cdk-experimental/scrolling/tsconfig-build.json index e289b7218442..cbf38299626b 100644 --- a/src/cdk-experimental/scrolling/tsconfig-build.json +++ b/src/cdk-experimental/scrolling/tsconfig-build.json @@ -1,8 +1,7 @@ { "extends": "../tsconfig-build", "files": [ - "public-api.ts", - "./typings.d.ts" + "public-api.ts" ], "angularCompilerOptions": { "annotateForClosureCompiler": true, diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index eb0454df5cd3..cbe7552a1704 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -1,558 +1,10 @@ -import {ArrayDataSource} from '@angular/cdk/collections'; -import {dispatchFakeEvent} from '@angular/cdk/testing'; -import {Component, Input, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import {CdkVirtualScrollViewport, ScrollingModule} from '@angular/cdk/scrolling'; +import {Component, Input, ViewChild, ViewEncapsulation} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; -import {animationFrameScheduler, Subject} from 'rxjs'; -import {CdkVirtualScrollViewport, CdkVirtualForOf, ScrollingModule} from './index'; +import {ScrollingModule as ExperimentalScrollingModule} from './scrolling-module'; describe('CdkVirtualScrollViewport', () => { - describe ('with FixedSizeVirtualScrollStrategy', () => { - let fixture: ComponentFixture; - let testComponent: FixedSizeVirtualScroll; - let viewport: CdkVirtualScrollViewport; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ScrollingModule], - declarations: [FixedSizeVirtualScroll], - }).compileComponents(); - - fixture = TestBed.createComponent(FixedSizeVirtualScroll); - testComponent = fixture.componentInstance; - viewport = testComponent.viewport; - }); - - it('should render initial state', fakeAsync(() => { - finishInit(fixture); - - const contentWrapper = - viewport.elementRef.nativeElement.querySelector('.cdk-virtual-scroll-content-wrapper')!; - expect(contentWrapper.children.length) - .toBe(4, 'should render 4 50px items to fill 200px space'); - })); - - it('should get the data length', fakeAsync(() => { - finishInit(fixture); - - expect(viewport.getDataLength()).toBe(testComponent.items.length); - })); - - it('should get the viewport size', fakeAsync(() => { - finishInit(fixture); - - expect(viewport.getViewportSize()).toBe(testComponent.viewportSize); - })); - - it('should update viewport size', fakeAsync(() => { - testComponent.viewportSize = 300; - fixture.detectChanges(); - flush(); - viewport.checkViewportSize(); - expect(viewport.getViewportSize()).toBe(300); - - testComponent.viewportSize = 500; - fixture.detectChanges(); - flush(); - viewport.checkViewportSize(); - expect(viewport.getViewportSize()).toBe(500); - })); - - it('should get the rendered range', fakeAsync(() => { - finishInit(fixture); - - expect(viewport.getRenderedRange()) - .toEqual({start: 0, end: 4}, 'should render the first 4 50px items to fill 200px space'); - })); - - it('should get the rendered content offset', fakeAsync(() => { - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize + 5); - fixture.detectChanges(); - flush(); - - expect(viewport.getOffsetToRenderedContentStart()).toBe(testComponent.itemSize, - 'should have 50px offset since first 50px item is not rendered'); - })); - - it('should get the scroll offset', fakeAsync(() => { - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize + 5); - fixture.detectChanges(); - flush(); - - expect(viewport.measureScrollOffset()).toBe(testComponent.itemSize + 5); - })); - - it('should get the rendered content size', fakeAsync(() => { - finishInit(fixture); - - expect(viewport.measureRenderedContentSize()) - .toBe(testComponent.viewportSize, - 'should render 4 50px items with combined size of 200px to fill 200px space'); - })); - - it('should measure range size', fakeAsync(() => { - finishInit(fixture); - - expect(viewport.measureRangeSize({start: 1, end: 3})) - .toBe(testComponent.itemSize * 2, 'combined size of 2 50px items should be 100px'); - })); - - it('should set total content size', fakeAsync(() => { - finishInit(fixture); - - viewport.setTotalContentSize(10000); - flush(); - fixture.detectChanges(); - - expect(viewport.elementRef.nativeElement.scrollHeight).toBe(10000); - })); - - it('should set rendered range', fakeAsync(() => { - finishInit(fixture); - viewport.setRenderedRange({start: 2, end: 3}); - fixture.detectChanges(); - flush(); - - const items = fixture.elementRef.nativeElement.querySelectorAll('.item'); - expect(items.length).toBe(1, 'Expected 1 item to be rendered'); - expect(items[0].innerText.trim()).toBe('2 - 2', 'Expected item with index 2 to be rendered'); - })); - - it('should set content offset to top of content', fakeAsync(() => { - finishInit(fixture); - viewport.setRenderedContentOffset(10, 'to-start'); - fixture.detectChanges(); - flush(); - - expect(viewport.getOffsetToRenderedContentStart()).toBe(10); - })); - - it('should set content offset to bottom of content', fakeAsync(() => { - finishInit(fixture); - const contentSize = viewport.measureRenderedContentSize(); - - expect(contentSize).toBeGreaterThan(0); - - viewport.setRenderedContentOffset(contentSize + 10, 'to-end'); - fixture.detectChanges(); - flush(); - - expect(viewport.getOffsetToRenderedContentStart()).toBe(10); - })); - - it('should set scroll offset', fakeAsync(() => { - finishInit(fixture); - viewport.setScrollOffset(testComponent.itemSize * 2); - fixture.detectChanges(); - flush(); - - triggerScroll(viewport); - fixture.detectChanges(); - flush(); - - expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); - expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - })); - - it('should scroll to offset', fakeAsync(() => { - finishInit(fixture); - viewport.scrollToOffset(testComponent.itemSize * 2); - - triggerScroll(viewport); - fixture.detectChanges(); - flush(); - - expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); - expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - })); - - it('should scroll to index', fakeAsync(() => { - finishInit(fixture); - viewport.scrollToIndex(2); - - triggerScroll(viewport); - fixture.detectChanges(); - flush(); - - expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); - expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - })); - - it('should scroll to offset in horizontal mode', fakeAsync(() => { - testComponent.orientation = 'horizontal'; - finishInit(fixture); - viewport.scrollToOffset(testComponent.itemSize * 2); - - triggerScroll(viewport); - fixture.detectChanges(); - flush(); - - expect(viewport.elementRef.nativeElement.scrollLeft).toBe(testComponent.itemSize * 2); - expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - })); - - it('should scroll to index in horizontal mode', fakeAsync(() => { - testComponent.orientation = 'horizontal'; - finishInit(fixture); - viewport.scrollToIndex(2); - - triggerScroll(viewport); - fixture.detectChanges(); - flush(); - - expect(viewport.elementRef.nativeElement.scrollLeft).toBe(testComponent.itemSize * 2); - expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - })); - - it('should output scrolled index', fakeAsync(() => { - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize * 2 - 1); - fixture.detectChanges(); - flush(); - - expect(testComponent.scrolledToIndex).toBe(1); - - triggerScroll(viewport, testComponent.itemSize * 2); - fixture.detectChanges(); - flush(); - - expect(testComponent.scrolledToIndex).toBe(2); - })); - - it('should update viewport as user scrolls down', fakeAsync(() => { - finishInit(fixture); - - const maxOffset = - testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; - for (let offset = 0; offset <= maxOffset; offset += 10) { - triggerScroll(viewport, offset); - fixture.detectChanges(); - flush(); - - const expectedRange = { - start: Math.floor(offset / testComponent.itemSize), - end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) - }; - expect(viewport.getRenderedRange()) - .toEqual(expectedRange, - `rendered range should match expected value at scroll offset ${offset}`); - expect(viewport.getOffsetToRenderedContentStart()) - .toBe(expectedRange.start * testComponent.itemSize, - `rendered content offset should match expected value at scroll offset ${offset}`); - expect(viewport.measureRenderedContentSize()) - .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, - `rendered content size should match expected value at offset ${offset}`); - } - })); - - it('should update viewport as user scrolls up', fakeAsync(() => { - finishInit(fixture); - - const maxOffset = - testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; - for (let offset = maxOffset; offset >= 0; offset -= 10) { - triggerScroll(viewport, offset); - fixture.detectChanges(); - flush(); - - const expectedRange = { - start: Math.floor(offset / testComponent.itemSize), - end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) - }; - expect(viewport.getRenderedRange()) - .toEqual(expectedRange, - `rendered range should match expected value at scroll offset ${offset}`); - expect(viewport.getOffsetToRenderedContentStart()) - .toBe(expectedRange.start * testComponent.itemSize, - `rendered content offset should match expected value at scroll offset ${offset}`); - expect(viewport.measureRenderedContentSize()) - .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, - `rendered content size should match expected value at offset ${offset}`); - } - })); - - it('should render buffer element at the end when scrolled to the top', fakeAsync(() => { - testComponent.bufferSize = 1; - finishInit(fixture); - - expect(viewport.getRenderedRange()).toEqual({start: 0, end: 5}, - 'should render the first 5 50px items to fill 200px space, plus one buffer element at' + - ' the end'); - })); - - it('should render buffer element at the start and end when scrolled to the middle', - fakeAsync(() => { - testComponent.bufferSize = 1; - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize * 2); - fixture.detectChanges(); - flush(); - - expect(viewport.getRenderedRange()).toEqual({start: 1, end: 7}, - 'should render 6 50px items to fill 200px space, plus one buffer element at the' + - ' start and end'); - })); - - it('should render buffer element at the start when scrolled to the bottom', fakeAsync(() => { - testComponent.bufferSize = 1; - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize * 6); - fixture.detectChanges(); - flush(); - - expect(viewport.getRenderedRange()).toEqual({start: 5, end: 10}, - 'should render the last 5 50px items to fill 200px space, plus one buffer element at' + - ' the start'); - })); - - it('should handle dynamic item size', fakeAsync(() => { - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize * 2); - fixture.detectChanges(); - flush(); - - expect(viewport.getRenderedRange()) - .toEqual({start: 2, end: 6}, 'should render 4 50px items to fill 200px space'); - - testComponent.itemSize *= 2; - fixture.detectChanges(); - flush(); - - expect(viewport.getRenderedRange()) - .toEqual({start: 1, end: 3}, 'should render 2 100px items to fill 200px space'); - })); - - it('should handle dynamic buffer size', fakeAsync(() => { - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize * 2); - fixture.detectChanges(); - flush(); - - expect(viewport.getRenderedRange()) - .toEqual({start: 2, end: 6}, 'should render 4 50px items to fill 200px space'); - - testComponent.bufferSize = 1; - fixture.detectChanges(); - flush(); - - expect(viewport.getRenderedRange()) - .toEqual({start: 1, end: 7}, 'should expand to 1 buffer element on each side'); - })); - - it('should handle dynamic item array', fakeAsync(() => { - finishInit(fixture); - triggerScroll(viewport, testComponent.itemSize * 6); - fixture.detectChanges(); - flush(); - - expect(viewport.getOffsetToRenderedContentStart()) - .toBe(testComponent.itemSize * 6, 'should be scrolled to bottom of 10 item list'); - - testComponent.items = Array(5).fill(0); - fixture.detectChanges(); - flush(); - - triggerScroll(viewport); - fixture.detectChanges(); - flush(); - - expect(viewport.getOffsetToRenderedContentStart()) - .toBe(testComponent.itemSize, 'should be scrolled to bottom of 5 item list'); - })); - - it('should update viewport as user scrolls right in horizontal mode', fakeAsync(() => { - testComponent.orientation = 'horizontal'; - finishInit(fixture); - - const maxOffset = - testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; - for (let offset = 0; offset <= maxOffset; offset += 10) { - triggerScroll(viewport, offset); - fixture.detectChanges(); - flush(); - - const expectedRange = { - start: Math.floor(offset / testComponent.itemSize), - end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) - }; - expect(viewport.getRenderedRange()) - .toEqual(expectedRange, - `rendered range should match expected value at scroll offset ${offset}`); - expect(viewport.getOffsetToRenderedContentStart()) - .toBe(expectedRange.start * testComponent.itemSize, - `rendered content offset should match expected value at scroll offset ${offset}`); - expect(viewport.measureRenderedContentSize()) - .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, - `rendered content size should match expected value at offset ${offset}`); - } - })); - - it('should update viewport as user scrolls left in horizontal mode', fakeAsync(() => { - testComponent.orientation = 'horizontal'; - finishInit(fixture); - - const maxOffset = - testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; - for (let offset = maxOffset; offset >= 0; offset -= 10) { - triggerScroll(viewport, offset); - fixture.detectChanges(); - flush(); - - const expectedRange = { - start: Math.floor(offset / testComponent.itemSize), - end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) - }; - expect(viewport.getRenderedRange()) - .toEqual(expectedRange, - `rendered range should match expected value at scroll offset ${offset}`); - expect(viewport.getOffsetToRenderedContentStart()) - .toBe(expectedRange.start * testComponent.itemSize, - `rendered content offset should match expected value at scroll offset ${offset}`); - expect(viewport.measureRenderedContentSize()) - .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, - `rendered content size should match expected value at offset ${offset}`); - } - })); - - 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(); - flush(); - - 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); - - 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 by default', fakeAsync(() => { - testComponent.items = []; - spyOn(testComponent.virtualForViewContainer, 'detach').and.callThrough(); - finishInit(fixture); - - testComponent.items = [0]; - fixture.detectChanges(); - flush(); - - expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); - - testComponent.items = [1]; - fixture.detectChanges(); - flush(); - - expect(testComponent.virtualForViewContainer.detach).toHaveBeenCalled(); - })); - - it('should trackBy index when specified', fakeAsync(() => { - testComponent.trackBy = i => i; - testComponent.items = []; - spyOn(testComponent.virtualForViewContainer, 'detach').and.callThrough(); - finishInit(fixture); - - testComponent.items = [0]; - fixture.detectChanges(); - flush(); - - expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); - - testComponent.items = [1]; - fixture.detectChanges(); - flush(); - - expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); - })); - - it('should recycle views when template cache is large enough to accommodate', fakeAsync(() => { - testComponent.trackBy = i => i; - const spy = - spyOn(testComponent.virtualForViewContainer, 'createEmbeddedView').and.callThrough(); - finishInit(fixture); - - // Should create views for the initial rendered items. - expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); - - spy.calls.reset(); - triggerScroll(viewport, 10); - fixture.detectChanges(); - flush(); - - // 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.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); - - spy.calls.reset(); - const maxOffset = - testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; - for (let offset = 10; offset <= maxOffset; offset += 10) { - triggerScroll(viewport, offset); - fixture.detectChanges(); - flush(); - } - - // 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.virtualForViewContainer.createEmbeddedView).not.toHaveBeenCalled(); - })); - - it('should not recycle views when template cache is full', fakeAsync(() => { - testComponent.trackBy = i => i; - testComponent.templateCacheSize = 0; - const spy = - spyOn(testComponent.virtualForViewContainer, 'createEmbeddedView').and.callThrough(); - finishInit(fixture); - - // Should create views for the initial rendered items. - expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); - - spy.calls.reset(); - triggerScroll(viewport, 10); - fixture.detectChanges(); - flush(); - - // 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.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); - - spy.calls.reset(); - const maxOffset = - testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; - for (let offset = 10; offset <= maxOffset; offset += 10) { - triggerScroll(viewport, offset); - fixture.detectChanges(); - flush(); - } - - // 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.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(5); - })); - }); - describe ('with AutoSizeVirtualScrollStrategy', () => { let fixture: ComponentFixture; let testComponent: AutoSizeVirtualScroll; @@ -560,7 +12,7 @@ describe('CdkVirtualScrollViewport', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ScrollingModule], + imports: [ScrollingModule, ExperimentalScrollingModule], declarations: [AutoSizeVirtualScroll], }).compileComponents(); @@ -605,69 +57,6 @@ function finishInit(fixture: ComponentFixture) { flush(); } -/** Trigger a scroll event on the viewport (optionally setting a new scroll offset). */ -function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { - if (offset !== undefined) { - if (viewport.orientation == 'horizontal') { - viewport.elementRef.nativeElement.scrollLeft = offset; - } else { - viewport.elementRef.nativeElement.scrollTop = offset; - } - } - dispatchFakeEvent(viewport.elementRef.nativeElement, 'scroll'); - animationFrameScheduler.flush(); -} - - -@Component({ - template: ` - -
- {{i}} - {{item}} -
-
- `, - 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 FixedSizeVirtualScroll { - @ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport; - @ViewChild(CdkVirtualForOf, {read: ViewContainerRef}) virtualForViewContainer: 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); - @Input() trackBy; - @Input() templateCacheSize = 20; - - scrolledToIndex = 0; - - get viewportWidth() { - return this.orientation == 'horizontal' ? this.viewportSize : this.viewportCrossSize; - } - - get viewportHeight() { - return this.orientation == 'horizontal' ? this.viewportCrossSize : this.viewportSize; - } -} @Component({ template: ` diff --git a/src/cdk-experimental/scrolling/virtual-scroll.md b/src/cdk-experimental/scrolling/virtual-scroll.md deleted file mode 100644 index 9f892254573e..000000000000 --- a/src/cdk-experimental/scrolling/virtual-scroll.md +++ /dev/null @@ -1,162 +0,0 @@ -**Warning: this component is still experimental. It may have bugs and the API may change at any -time** - -`` displays large lists of elements performantly by only -rendering the items that fit on-screen. Loading hundreds of elements can be slow in any browser; -virtual scrolling enables a performant way to simulate all items being rendered by making the -height of the container element the same as the height of total number of elements to be rendered, -and then only rendering the items in view. Virtual scrolling is different from strategies like -infinite scroll where it renders a set amount of elements and then when you hit the end renders the -rest. - - -For some example usages, -[see the demo app](https://github.com/angular/material2/tree/master/src/demo-app/virtual-scroll). - -### Creating items in the viewport -`*cdkVirtualFor` replaces `*ngFor` inside of a ``, supporting the exact -same API as [`*ngFor`](https://angular.io/api/common/NgForOf). -The simplest usage just specifies the list of items: - -```html - -
{{item}}
-
-``` - -`*cdkVirtualFor` makes the following context variables available to the template: - -| Context variable | Description | -|------------------|----------------------------------------------------| -| `index` | The index of the item in the data source. | -| `count` | The total number of items in the data source. | -| `first` | Whether this is the first item in the data source. | -| `last` | Whether this is the last item in the data source. | -| `even` | Whether the `index` is even. | -| `odd` | Whether the `index` is odd. | - -All of these apply to the index of the item in the data source, not the index in the rendered -portion of the data. - -```html - -
- {{item}} ({{index}} of {{count}}) -
-
-``` - -A `trackBy` function can be specified and works the same as the `*ngFor` `trackBy`. The `index` -passed to the tracking function will be the index in the data source, not the index in the rendered -portion. - -#### View recycling -To improve rendering performance, `*cdkVirtualFor` caches previously created views after -they are no longer needed. When a new view would normally be created, a cached view -is reused instead. The size of the view cache can be adjusted via the `templateCacheSize` -property; setting this size to `0` disables caching. If your templates are expensive in terms of -memory you may wish to reduce this number to avoid spending too much memory on the template cache. - -```html - -
{{item}}
-
-``` - -#### Specifying data -`*cdkVirtualFor` accepts data from an `Array`, `Observable`, or `DataSource`. The -`DataSource` for the virtual scroll is the same one used by the table and tree components. A -`DataSource` is simply an abstract class that has two methods: `connect` and `disconnect`. The -`connect` method will be called by the virtual scroll viewport to receive a stream that emits the -data array that should be rendered. The viewport will call `disconnect` when the viewport is -destroyed, which may be the right time to clean up any subscriptions that were registered during the -connect process. - -### Scrolling over fixed size items -When all items are the same fixed size, you can use the `FixedSizeVirtualScrollStrategy`. This can -be easily added to your viewport using the `itemSize` directive. The fixed size viewport strategy is -less flexible than the autosize strategy because it requires all items to be the same size, but the -advantage of this constraint is that it allows for better performance, since items do not need to be -measured as they are rendered. - -```html - - ... - -``` - -The fixed size strategy also supports setting the buffer size, i.e. the number of items rendered -beyond the edge of the viewport. This can be adjusted by setting the `bufferSize` input. If -`bufferSize` is not specified it defaults to 5 items. - -```html - - ... - -``` - -**Note: The fixed size strategy will likely be changed to allow specifying a separate -`minBufferPx` and `addBufferPx` like the autosize strategy** - -### Scrolling over items with different sizes -When the items have different or unknown sizes, you can use the `AutoSizeVirtualScrollStrategy`. -This can be added to your viewport by using the `autosize` directive. - -```html - - ... - -``` - -The `autosize` strategy is configured through two inputs: `minBufferPx` and `addBufferPx`. - -**`minBufferPx`** determines the minimum space outside virtual scrolling viewport that will be -filled with content. Increasing this will increase the amount of content a user will see before more -content must be rendered. However, too large a value will cause more content to be rendered than is -necessary. - -**`addBufferPx`** determines the amount of content that will be added incrementally as the viewport -is scrolled. This should be greater than the size of `minBufferPx` so that one "render" is needed at -a time. - -```html - - ... - -``` - -Because the auto size strategy needs to measure the size of the elements, its performance may not -be as good as the fixed size strategy. - -### Viewport orientation -The virtual-scroll viewport defaults to a vertical orientation, but can also be set to -`orientation="horizontal"`. When changing the orientation, ensure that the item are laid -out horizontally via CSS. To do this you may want to target CSS at -`.cdk-virtual-scroll-content-wrapper` which is the wrapper element that contains the rendered -content. - -```html - - ... - -``` - -### Elements with parent tag requirements -Some HTML elements such as `` and `
  • ` have limitations on the kinds of parent elements they -can be placed inside. To enable virtual scrolling over these type of elements, place the elements in -their proper parent, and then wrap the whole thing in a `cdk-virtual-scroll-viewport`. Be careful -that the parent does not introduce additional space (e.g. via `margin` or `padding`) as it will -interfere with the scrolling. - -```html - - - - - - -
    {{row.first}}{{row.second}}
    -
    -``` diff --git a/src/cdk/overlay/overlay-module.ts b/src/cdk/overlay/overlay-module.ts index 4dcfc324e5f5..a0c621fd1258 100644 --- a/src/cdk/overlay/overlay-module.ts +++ b/src/cdk/overlay/overlay-module.ts @@ -8,7 +8,7 @@ import {BidiModule} from '@angular/cdk/bidi'; import {PortalModule} from '@angular/cdk/portal'; -import {ScrollDispatchModule, VIEWPORT_RULER_PROVIDER} from '@angular/cdk/scrolling'; +import {ScrollingModule, VIEWPORT_RULER_PROVIDER} from '@angular/cdk/scrolling'; import {NgModule, Provider} from '@angular/core'; import {OVERLAY_KEYBOARD_DISPATCHER_PROVIDER} from './keyboard/overlay-keyboard-dispatcher'; import {Overlay} from './overlay'; @@ -22,8 +22,8 @@ import {OverlayPositionBuilder} from './position/overlay-position-builder'; @NgModule({ - imports: [BidiModule, PortalModule, ScrollDispatchModule], - exports: [CdkConnectedOverlay, CdkOverlayOrigin, ScrollDispatchModule], + imports: [BidiModule, PortalModule, ScrollingModule], + exports: [CdkConnectedOverlay, CdkOverlayOrigin, ScrollingModule], declarations: [CdkConnectedOverlay, CdkOverlayOrigin], providers: [ Overlay, diff --git a/src/cdk/overlay/position/connected-position-strategy.spec.ts b/src/cdk/overlay/position/connected-position-strategy.spec.ts index 4a67c1ff2142..c40e3296a718 100644 --- a/src/cdk/overlay/position/connected-position-strategy.spec.ts +++ b/src/cdk/overlay/position/connected-position-strategy.spec.ts @@ -1,5 +1,5 @@ import {ComponentPortal, PortalModule} from '@angular/cdk/portal'; -import {CdkScrollable, ScrollDispatchModule} from '@angular/cdk/scrolling'; +import {CdkScrollable, ScrollingModule} from '@angular/cdk/scrolling'; import {MockNgZone} from '@angular/cdk/testing'; import {Component, ElementRef, NgModule, NgZone} from '@angular/core'; import {inject, TestBed} from '@angular/core/testing'; @@ -31,7 +31,7 @@ describe('ConnectedPositionStrategy', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ScrollDispatchModule, OverlayModule, OverlayTestModule], + imports: [ScrollingModule, OverlayModule, OverlayTestModule], providers: [{provide: NgZone, useFactory: () => zone = new MockNgZone()}] }); diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts index 8edb456a4ff8..26efa78890a5 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts @@ -1,5 +1,5 @@ import {ComponentPortal, PortalModule} from '@angular/cdk/portal'; -import {CdkScrollable, ScrollDispatchModule} from '@angular/cdk/scrolling'; +import {CdkScrollable, ScrollingModule} from '@angular/cdk/scrolling'; import {MockNgZone} from '@angular/cdk/testing'; import {Component, ElementRef, NgModule, NgZone} from '@angular/core'; import {inject, TestBed} from '@angular/core/testing'; @@ -30,7 +30,7 @@ describe('FlexibleConnectedPositionStrategy', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ScrollDispatchModule, OverlayModule, OverlayTestModule], + imports: [ScrollingModule, OverlayModule, OverlayTestModule], providers: [{provide: NgZone, useFactory: () => zone = new MockNgZone()}] }); diff --git a/src/cdk/scrolling/BUILD.bazel b/src/cdk/scrolling/BUILD.bazel index aa83f52ab8df..04b6c898ded5 100644 --- a/src/cdk/scrolling/BUILD.bazel +++ b/src/cdk/scrolling/BUILD.bazel @@ -7,13 +7,21 @@ ng_module( name = "scrolling", srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]), module_name = "@angular/cdk/scrolling", + assets = [":virtual-scroll-viewport.css"] + glob(["**/*.html"]), deps = [ + "//src/cdk/coercion", + "//src/cdk/collections", "//src/cdk/platform", "@rxjs", ], tsconfig = "//src/cdk:tsconfig-build.json", ) +sass_binary( + name = "virtual_scroll_viewport_scss", + src = "virtual-scroll-viewport.scss", +) + ts_library( name = "scrolling_test_sources", testonly = 1, diff --git a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts b/src/cdk/scrolling/fixed-size-virtual-scroll.ts similarity index 100% rename from src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts rename to src/cdk/scrolling/fixed-size-virtual-scroll.ts diff --git a/src/cdk/scrolling/index.ts b/src/cdk/scrolling/index.ts index 676ca90f1ffa..ed7fe9c90d76 100644 --- a/src/cdk/scrolling/index.ts +++ b/src/cdk/scrolling/index.ts @@ -7,3 +7,9 @@ */ export * from './public-api'; + +/** + * @deprecated ScrollingModule has been renamed to ScrollingModule. + * @deletion-target 8.0.0 + */ +export {ScrollingModule as ScrollDispatchModule} from './scrolling-module'; diff --git a/src/cdk/scrolling/public-api.ts b/src/cdk/scrolling/public-api.ts index ffbd65d14e06..2c1cf815d1a0 100644 --- a/src/cdk/scrolling/public-api.ts +++ b/src/cdk/scrolling/public-api.ts @@ -6,7 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ +export * from './fixed-size-virtual-scroll'; export * from './scroll-dispatcher'; export * from './scrollable'; -export * from './viewport-ruler'; export * from './scrolling-module'; +export * from './viewport-ruler'; +export * from './virtual-for-of'; +export * from './virtual-scroll-strategy'; +export * from './virtual-scroll-viewport'; diff --git a/src/cdk/scrolling/scroll-dispatcher.spec.ts b/src/cdk/scrolling/scroll-dispatcher.spec.ts index 6e5624a7a190..cc416ffee8da 100644 --- a/src/cdk/scrolling/scroll-dispatcher.spec.ts +++ b/src/cdk/scrolling/scroll-dispatcher.spec.ts @@ -1,6 +1,6 @@ import {inject, TestBed, async, fakeAsync, ComponentFixture, tick} from '@angular/core/testing'; import {NgModule, Component, ViewChild, ElementRef} from '@angular/core'; -import {CdkScrollable, ScrollDispatcher, ScrollDispatchModule} from './public-api'; +import {CdkScrollable, ScrollDispatcher, ScrollingModule} from './public-api'; import {dispatchFakeEvent} from '@angular/cdk/testing'; describe('ScrollDispatcher', () => { @@ -250,7 +250,7 @@ class NestedScrollingComponent { const TEST_COMPONENTS = [ScrollingComponent, NestedScrollingComponent]; @NgModule({ - imports: [ScrollDispatchModule], + imports: [ScrollingModule], providers: [ScrollDispatcher], exports: TEST_COMPONENTS, declarations: TEST_COMPONENTS, diff --git a/src/cdk/scrolling/scrolling-module.ts b/src/cdk/scrolling/scrolling-module.ts index 955e1751e562..ad2282d6d10b 100644 --- a/src/cdk/scrolling/scrolling-module.ts +++ b/src/cdk/scrolling/scrolling-module.ts @@ -8,11 +8,24 @@ import {PlatformModule} from '@angular/cdk/platform'; import {NgModule} from '@angular/core'; +import {CdkFixedSizeVirtualScroll} from './fixed-size-virtual-scroll'; import {CdkScrollable} from './scrollable'; +import {CdkVirtualForOf} from './virtual-for-of'; +import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; @NgModule({ imports: [PlatformModule], - exports: [CdkScrollable], - declarations: [CdkScrollable], + exports: [ + CdkFixedSizeVirtualScroll, + CdkScrollable, + CdkVirtualForOf, + CdkVirtualScrollViewport, + ], + declarations: [ + CdkFixedSizeVirtualScroll, + CdkScrollable, + CdkVirtualForOf, + CdkVirtualScrollViewport, + ], }) -export class ScrollDispatchModule {} +export class ScrollingModule {} diff --git a/src/cdk/scrolling/scrolling.md b/src/cdk/scrolling/scrolling.md index c2518fb93706..281002e54ecd 100644 --- a/src/cdk/scrolling/scrolling.md +++ b/src/cdk/scrolling/scrolling.md @@ -8,3 +8,133 @@ The `cdkScrollable` directive should be applied to any element that acts as a sc This marks the element as a `Scrollable` and registers it with the `ScrollDispatcher`. The dispatcher, then, allows components to share both event listeners and knowledge of all of the scrollable containers in the application. + +### ViewportRuler +The `ViewportRuler` is a service that can be injected and used to measure the bounds of the browser +viewport. + +### Virtual scrolling +The `` displays large lists of elements performantly by only +rendering the items that fit on-screen. Loading hundreds of elements can be slow in any browser; +virtual scrolling enables a performant way to simulate all items being rendered by making the +height of the container element the same as the height of total number of elements to be rendered, +and then only rendering the items in view. Virtual scrolling is different from strategies like +infinite scroll where it renders a set amount of elements and then when you hit the end renders the +rest. + +#### Creating items in the viewport +`*cdkVirtualFor` replaces `*ngFor` inside of a ``, supporting the exact +same API as [`*ngFor`](https://angular.io/api/common/NgForOf). The simplest usage just specifies the +list of items: + +```html + +
    {{item}}
    +
    +``` + +`*cdkVirtualFor` makes the following context variables available to the template: + +| Context variable | Description | +|------------------|----------------------------------------------------| +| `index` | The index of the item in the data source. | +| `count` | The total number of items in the data source. | +| `first` | Whether this is the first item in the data source. | +| `last` | Whether this is the last item in the data source. | +| `even` | Whether the `index` is even. | +| `odd` | Whether the `index` is odd. | + +All of these apply to the index of the item in the data source, not the index in the rendered +portion of the data. + +```html + +
    + {{item}} ({{index}} of {{count}}) +
    +
    +``` + +A `trackBy` function can be specified and works the same as the `*ngFor` `trackBy`. The `index` +passed to the tracking function will be the index in the data source, not the index in the rendered +portion. + +##### View recycling +To improve rendering performance, `*cdkVirtualFor` caches previously created views after +they are no longer needed. When a new view would normally be created, a cached view +is reused instead. The size of the view cache can be adjusted via the `templateCacheSize` +property; setting this size to `0` disables caching. If your templates are expensive in terms of +memory you may wish to reduce this number to avoid spending too much memory on the template cache. + +```html + +
    {{item}}
    +
    +``` + +##### Specifying data +`*cdkVirtualFor` accepts data from an `Array`, `Observable`, or `DataSource`. The +`DataSource` for the virtual scroll is the same one used by the table and tree components. A +`DataSource` is simply an abstract class that has two methods: `connect` and `disconnect`. The +`connect` method will be called by the virtual scroll viewport to receive a stream that emits the +data array that should be rendered. The viewport will call `disconnect` when the viewport is +destroyed, which may be the right time to clean up any subscriptions that were registered during the +connect process. + +#### Scrolling over fixed size items +When all items are the same fixed size, you can use the `FixedSizeVirtualScrollStrategy`. This can +be easily added to your viewport using the `itemSize` directive. The advantage of this constraint is +that it allows for better performance, since items do not need to be measured as they are rendered. + +```html + + ... + +``` + +The fixed size strategy also supports setting the buffer size, i.e. the number of items rendered +beyond the edge of the viewport. This can be adjusted by setting the `bufferSize` input. If +`bufferSize` is not specified it defaults to 5 items. + +```html + + ... + +``` + +Other virtual scrolling strategies can be implemented by extending `VirtualScrollStrategy`. An +autosize strategy that works on elements of differing sizes is currently being developed in +`@angular/cdk-experimental`, but it is not ready for production use yet. + +### Viewport orientation +The virtual-scroll viewport defaults to a vertical orientation, but can also be set to +`orientation="horizontal"`. When changing the orientation, ensure that the item are laid +out horizontally via CSS. To do this you may want to target CSS at +`.cdk-virtual-scroll-content-wrapper` which is the wrapper element that contains the rendered +content. + +```html + + ... + +``` + +### Elements with parent tag requirements +Some HTML elements such as `` and `
  • ` have limitations on the kinds of parent elements they +can be placed inside. To enable virtual scrolling over these type of elements, place the elements in +their proper parent, and then wrap the whole thing in a `cdk-virtual-scroll-viewport`. Be careful +that the parent does not introduce additional space (e.g. via `margin` or `padding`) as it will +interfere with the scrolling. + +```html + + + + + + +
    {{row.first}}{{row.second}}
    +
    +``` diff --git a/src/cdk/scrolling/tsconfig-build.json b/src/cdk/scrolling/tsconfig-build.json index 40c5aba7f877..18b45049202d 100644 --- a/src/cdk/scrolling/tsconfig-build.json +++ b/src/cdk/scrolling/tsconfig-build.json @@ -1,7 +1,8 @@ { "extends": "../tsconfig-build", "files": [ - "public-api.ts" + "public-api.ts", + "./typings.d.ts" ], "angularCompilerOptions": { "annotateForClosureCompiler": true, diff --git a/src/cdk-experimental/scrolling/typings.d.ts b/src/cdk/scrolling/typings.d.ts similarity index 100% rename from src/cdk-experimental/scrolling/typings.d.ts rename to src/cdk/scrolling/typings.d.ts diff --git a/src/cdk/scrolling/viewport-ruler.spec.ts b/src/cdk/scrolling/viewport-ruler.spec.ts index 740880cf273c..5877c4df8e6e 100644 --- a/src/cdk/scrolling/viewport-ruler.spec.ts +++ b/src/cdk/scrolling/viewport-ruler.spec.ts @@ -1,5 +1,5 @@ import {TestBed, inject, fakeAsync, tick} from '@angular/core/testing'; -import {ScrollDispatchModule} from './public-api'; +import {ScrollingModule} from './public-api'; import {ViewportRuler} from './viewport-ruler'; import {dispatchFakeEvent} from '@angular/cdk/testing'; @@ -24,7 +24,7 @@ describe('ViewportRuler', () => { veryLargeElement.style.height = '6000px'; beforeEach(() => TestBed.configureTestingModule({ - imports: [ScrollDispatchModule], + imports: [ScrollingModule], providers: [ViewportRuler] })); diff --git a/src/cdk/scrolling/viewport.md b/src/cdk/scrolling/viewport.md deleted file mode 100644 index 023088a5552a..000000000000 --- a/src/cdk/scrolling/viewport.md +++ /dev/null @@ -1,3 +0,0 @@ -### Viewport - -A utility for getting the bounds of the browser viewport. \ No newline at end of file diff --git a/src/cdk-experimental/scrolling/virtual-for-of.ts b/src/cdk/scrolling/virtual-for-of.ts similarity index 100% rename from src/cdk-experimental/scrolling/virtual-for-of.ts rename to src/cdk/scrolling/virtual-for-of.ts diff --git a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts b/src/cdk/scrolling/virtual-scroll-strategy.ts similarity index 100% rename from src/cdk-experimental/scrolling/virtual-scroll-strategy.ts rename to src/cdk/scrolling/virtual-scroll-strategy.ts diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.html b/src/cdk/scrolling/virtual-scroll-viewport.html similarity index 100% rename from src/cdk-experimental/scrolling/virtual-scroll-viewport.html rename to src/cdk/scrolling/virtual-scroll-viewport.html diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.scss b/src/cdk/scrolling/virtual-scroll-viewport.scss similarity index 100% rename from src/cdk-experimental/scrolling/virtual-scroll-viewport.scss rename to src/cdk/scrolling/virtual-scroll-viewport.scss diff --git a/src/cdk/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk/scrolling/virtual-scroll-viewport.spec.ts new file mode 100644 index 000000000000..f4aeb2cc7fb2 --- /dev/null +++ b/src/cdk/scrolling/virtual-scroll-viewport.spec.ts @@ -0,0 +1,631 @@ +import {ArrayDataSource} from '@angular/cdk/collections'; +import {CdkVirtualForOf, CdkVirtualScrollViewport, ScrollingModule} from '@angular/cdk/scrolling'; +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, Subject} from 'rxjs'; + + +describe('CdkVirtualScrollViewport', () => { + describe ('with FixedSizeVirtualScrollStrategy', () => { + let fixture: ComponentFixture; + let testComponent: FixedSizeVirtualScroll; + let viewport: CdkVirtualScrollViewport; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ScrollingModule], + declarations: [FixedSizeVirtualScroll], + }).compileComponents(); + + fixture = TestBed.createComponent(FixedSizeVirtualScroll); + testComponent = fixture.componentInstance; + viewport = testComponent.viewport; + }); + + it('should render initial state', fakeAsync(() => { + finishInit(fixture); + + const contentWrapper = + viewport.elementRef.nativeElement.querySelector('.cdk-virtual-scroll-content-wrapper')!; + expect(contentWrapper.children.length) + .toBe(4, 'should render 4 50px items to fill 200px space'); + })); + + it('should get the data length', fakeAsync(() => { + finishInit(fixture); + + expect(viewport.getDataLength()).toBe(testComponent.items.length); + })); + + it('should get the viewport size', fakeAsync(() => { + finishInit(fixture); + + expect(viewport.getViewportSize()).toBe(testComponent.viewportSize); + })); + + it('should update viewport size', fakeAsync(() => { + testComponent.viewportSize = 300; + fixture.detectChanges(); + flush(); + viewport.checkViewportSize(); + expect(viewport.getViewportSize()).toBe(300); + + testComponent.viewportSize = 500; + fixture.detectChanges(); + flush(); + viewport.checkViewportSize(); + expect(viewport.getViewportSize()).toBe(500); + })); + + it('should get the rendered range', fakeAsync(() => { + finishInit(fixture); + + expect(viewport.getRenderedRange()) + .toEqual({start: 0, end: 4}, 'should render the first 4 50px items to fill 200px space'); + })); + + it('should get the rendered content offset', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize + 5); + fixture.detectChanges(); + flush(); + + expect(viewport.getOffsetToRenderedContentStart()).toBe(testComponent.itemSize, + 'should have 50px offset since first 50px item is not rendered'); + })); + + it('should get the scroll offset', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize + 5); + fixture.detectChanges(); + flush(); + + expect(viewport.measureScrollOffset()).toBe(testComponent.itemSize + 5); + })); + + it('should get the rendered content size', fakeAsync(() => { + finishInit(fixture); + + expect(viewport.measureRenderedContentSize()) + .toBe(testComponent.viewportSize, + 'should render 4 50px items with combined size of 200px to fill 200px space'); + })); + + it('should measure range size', fakeAsync(() => { + finishInit(fixture); + + expect(viewport.measureRangeSize({start: 1, end: 3})) + .toBe(testComponent.itemSize * 2, 'combined size of 2 50px items should be 100px'); + })); + + it('should set total content size', fakeAsync(() => { + finishInit(fixture); + + viewport.setTotalContentSize(10000); + flush(); + fixture.detectChanges(); + + expect(viewport.elementRef.nativeElement.scrollHeight).toBe(10000); + })); + + it('should set rendered range', fakeAsync(() => { + finishInit(fixture); + viewport.setRenderedRange({start: 2, end: 3}); + fixture.detectChanges(); + flush(); + + const items = fixture.elementRef.nativeElement.querySelectorAll('.item'); + expect(items.length).toBe(1, 'Expected 1 item to be rendered'); + expect(items[0].innerText.trim()).toBe('2 - 2', 'Expected item with index 2 to be rendered'); + })); + + it('should set content offset to top of content', fakeAsync(() => { + finishInit(fixture); + viewport.setRenderedContentOffset(10, 'to-start'); + fixture.detectChanges(); + flush(); + + expect(viewport.getOffsetToRenderedContentStart()).toBe(10); + })); + + it('should set content offset to bottom of content', fakeAsync(() => { + finishInit(fixture); + const contentSize = viewport.measureRenderedContentSize(); + + expect(contentSize).toBeGreaterThan(0); + + viewport.setRenderedContentOffset(contentSize + 10, 'to-end'); + fixture.detectChanges(); + flush(); + + expect(viewport.getOffsetToRenderedContentStart()).toBe(10); + })); + + it('should set scroll offset', fakeAsync(() => { + finishInit(fixture); + viewport.setScrollOffset(testComponent.itemSize * 2); + fixture.detectChanges(); + flush(); + + triggerScroll(viewport); + fixture.detectChanges(); + flush(); + + expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); + expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); + })); + + it('should scroll to offset', fakeAsync(() => { + finishInit(fixture); + viewport.scrollToOffset(testComponent.itemSize * 2); + + triggerScroll(viewport); + fixture.detectChanges(); + flush(); + + expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); + expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); + })); + + it('should scroll to index', fakeAsync(() => { + finishInit(fixture); + viewport.scrollToIndex(2); + + triggerScroll(viewport); + fixture.detectChanges(); + flush(); + + expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); + expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); + })); + + it('should scroll to offset in horizontal mode', fakeAsync(() => { + testComponent.orientation = 'horizontal'; + finishInit(fixture); + viewport.scrollToOffset(testComponent.itemSize * 2); + + triggerScroll(viewport); + fixture.detectChanges(); + flush(); + + expect(viewport.elementRef.nativeElement.scrollLeft).toBe(testComponent.itemSize * 2); + expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); + })); + + it('should scroll to index in horizontal mode', fakeAsync(() => { + testComponent.orientation = 'horizontal'; + finishInit(fixture); + viewport.scrollToIndex(2); + + triggerScroll(viewport); + fixture.detectChanges(); + flush(); + + expect(viewport.elementRef.nativeElement.scrollLeft).toBe(testComponent.itemSize * 2); + expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); + })); + + it('should output scrolled index', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2 - 1); + fixture.detectChanges(); + flush(); + + expect(testComponent.scrolledToIndex).toBe(1); + + triggerScroll(viewport, testComponent.itemSize * 2); + fixture.detectChanges(); + flush(); + + expect(testComponent.scrolledToIndex).toBe(2); + })); + + it('should update viewport as user scrolls down', fakeAsync(() => { + finishInit(fixture); + + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = 0; offset <= maxOffset; offset += 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + flush(); + + const expectedRange = { + start: Math.floor(offset / testComponent.itemSize), + end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) + }; + expect(viewport.getRenderedRange()) + .toEqual(expectedRange, + `rendered range should match expected value at scroll offset ${offset}`); + expect(viewport.getOffsetToRenderedContentStart()) + .toBe(expectedRange.start * testComponent.itemSize, + `rendered content offset should match expected value at scroll offset ${offset}`); + expect(viewport.measureRenderedContentSize()) + .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, + `rendered content size should match expected value at offset ${offset}`); + } + })); + + it('should update viewport as user scrolls up', fakeAsync(() => { + finishInit(fixture); + + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = maxOffset; offset >= 0; offset -= 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + flush(); + + const expectedRange = { + start: Math.floor(offset / testComponent.itemSize), + end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) + }; + expect(viewport.getRenderedRange()) + .toEqual(expectedRange, + `rendered range should match expected value at scroll offset ${offset}`); + expect(viewport.getOffsetToRenderedContentStart()) + .toBe(expectedRange.start * testComponent.itemSize, + `rendered content offset should match expected value at scroll offset ${offset}`); + expect(viewport.measureRenderedContentSize()) + .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, + `rendered content size should match expected value at offset ${offset}`); + } + })); + + it('should render buffer element at the end when scrolled to the top', fakeAsync(() => { + testComponent.bufferSize = 1; + finishInit(fixture); + + expect(viewport.getRenderedRange()).toEqual({start: 0, end: 5}, + 'should render the first 5 50px items to fill 200px space, plus one buffer element at' + + ' the end'); + })); + + it('should render buffer element at the start and end when scrolled to the middle', + fakeAsync(() => { + testComponent.bufferSize = 1; + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2); + fixture.detectChanges(); + flush(); + + expect(viewport.getRenderedRange()).toEqual({start: 1, end: 7}, + 'should render 6 50px items to fill 200px space, plus one buffer element at the' + + ' start and end'); + })); + + it('should render buffer element at the start when scrolled to the bottom', fakeAsync(() => { + testComponent.bufferSize = 1; + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 6); + fixture.detectChanges(); + flush(); + + expect(viewport.getRenderedRange()).toEqual({start: 5, end: 10}, + 'should render the last 5 50px items to fill 200px space, plus one buffer element at' + + ' the start'); + })); + + it('should handle dynamic item size', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2); + fixture.detectChanges(); + flush(); + + expect(viewport.getRenderedRange()) + .toEqual({start: 2, end: 6}, 'should render 4 50px items to fill 200px space'); + + testComponent.itemSize *= 2; + fixture.detectChanges(); + flush(); + + expect(viewport.getRenderedRange()) + .toEqual({start: 1, end: 3}, 'should render 2 100px items to fill 200px space'); + })); + + it('should handle dynamic buffer size', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2); + fixture.detectChanges(); + flush(); + + expect(viewport.getRenderedRange()) + .toEqual({start: 2, end: 6}, 'should render 4 50px items to fill 200px space'); + + testComponent.bufferSize = 1; + fixture.detectChanges(); + flush(); + + expect(viewport.getRenderedRange()) + .toEqual({start: 1, end: 7}, 'should expand to 1 buffer element on each side'); + })); + + it('should handle dynamic item array', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 6); + fixture.detectChanges(); + flush(); + + expect(viewport.getOffsetToRenderedContentStart()) + .toBe(testComponent.itemSize * 6, 'should be scrolled to bottom of 10 item list'); + + testComponent.items = Array(5).fill(0); + fixture.detectChanges(); + flush(); + + triggerScroll(viewport); + fixture.detectChanges(); + flush(); + + expect(viewport.getOffsetToRenderedContentStart()) + .toBe(testComponent.itemSize, 'should be scrolled to bottom of 5 item list'); + })); + + it('should update viewport as user scrolls right in horizontal mode', fakeAsync(() => { + testComponent.orientation = 'horizontal'; + finishInit(fixture); + + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = 0; offset <= maxOffset; offset += 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + flush(); + + const expectedRange = { + start: Math.floor(offset / testComponent.itemSize), + end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) + }; + expect(viewport.getRenderedRange()) + .toEqual(expectedRange, + `rendered range should match expected value at scroll offset ${offset}`); + expect(viewport.getOffsetToRenderedContentStart()) + .toBe(expectedRange.start * testComponent.itemSize, + `rendered content offset should match expected value at scroll offset ${offset}`); + expect(viewport.measureRenderedContentSize()) + .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, + `rendered content size should match expected value at offset ${offset}`); + } + })); + + it('should update viewport as user scrolls left in horizontal mode', fakeAsync(() => { + testComponent.orientation = 'horizontal'; + finishInit(fixture); + + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = maxOffset; offset >= 0; offset -= 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + flush(); + + const expectedRange = { + start: Math.floor(offset / testComponent.itemSize), + end: Math.ceil((offset + testComponent.viewportSize) / testComponent.itemSize) + }; + expect(viewport.getRenderedRange()) + .toEqual(expectedRange, + `rendered range should match expected value at scroll offset ${offset}`); + expect(viewport.getOffsetToRenderedContentStart()) + .toBe(expectedRange.start * testComponent.itemSize, + `rendered content offset should match expected value at scroll offset ${offset}`); + expect(viewport.measureRenderedContentSize()) + .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize, + `rendered content size should match expected value at offset ${offset}`); + } + })); + + 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(); + flush(); + + 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); + + 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 by default', fakeAsync(() => { + testComponent.items = []; + spyOn(testComponent.virtualForViewContainer, 'detach').and.callThrough(); + finishInit(fixture); + + testComponent.items = [0]; + fixture.detectChanges(); + flush(); + + expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); + + testComponent.items = [1]; + fixture.detectChanges(); + flush(); + + expect(testComponent.virtualForViewContainer.detach).toHaveBeenCalled(); + })); + + it('should trackBy index when specified', fakeAsync(() => { + testComponent.trackBy = i => i; + testComponent.items = []; + spyOn(testComponent.virtualForViewContainer, 'detach').and.callThrough(); + finishInit(fixture); + + testComponent.items = [0]; + fixture.detectChanges(); + flush(); + + expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); + + testComponent.items = [1]; + fixture.detectChanges(); + flush(); + + expect(testComponent.virtualForViewContainer.detach).not.toHaveBeenCalled(); + })); + + it('should recycle views when template cache is large enough to accommodate', fakeAsync(() => { + testComponent.trackBy = i => i; + const spy = + spyOn(testComponent.virtualForViewContainer, 'createEmbeddedView').and.callThrough(); + finishInit(fixture); + + // Should create views for the initial rendered items. + expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); + + spy.calls.reset(); + triggerScroll(viewport, 10); + fixture.detectChanges(); + flush(); + + // 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.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); + + spy.calls.reset(); + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = 10; offset <= maxOffset; offset += 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + flush(); + } + + // 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.virtualForViewContainer.createEmbeddedView).not.toHaveBeenCalled(); + })); + + it('should not recycle views when template cache is full', fakeAsync(() => { + testComponent.trackBy = i => i; + testComponent.templateCacheSize = 0; + const spy = + spyOn(testComponent.virtualForViewContainer, 'createEmbeddedView').and.callThrough(); + finishInit(fixture); + + // Should create views for the initial rendered items. + expect(testComponent.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(4); + + spy.calls.reset(); + triggerScroll(viewport, 10); + fixture.detectChanges(); + flush(); + + // 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.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(1); + + spy.calls.reset(); + const maxOffset = + testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; + for (let offset = 10; offset <= maxOffset; offset += 10) { + triggerScroll(viewport, offset); + fixture.detectChanges(); + flush(); + } + + // 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.virtualForViewContainer.createEmbeddedView).toHaveBeenCalledTimes(5); + })); + }); +}); + + +/** Finish initializing the virtual scroll component at the beginning of a test. */ +function finishInit(fixture: ComponentFixture) { + // On the first cycle we render and measure the viewport. + fixture.detectChanges(); + flush(); + + // On the second cycle we render the items. + fixture.detectChanges(); + flush(); +} + +/** Trigger a scroll event on the viewport (optionally setting a new scroll offset). */ +function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { + if (offset !== undefined) { + if (viewport.orientation == 'horizontal') { + viewport.elementRef.nativeElement.scrollLeft = offset; + } else { + viewport.elementRef.nativeElement.scrollTop = offset; + } + } + dispatchFakeEvent(viewport.elementRef.nativeElement, 'scroll'); + animationFrameScheduler.flush(); +} + + +@Component({ + template: ` + +
    + {{i}} - {{item}} +
    +
    + `, + 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 FixedSizeVirtualScroll { + @ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport; + @ViewChild(CdkVirtualForOf, {read: ViewContainerRef}) virtualForViewContainer: 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); + @Input() trackBy; + @Input() templateCacheSize = 20; + + scrolledToIndex = 0; + + get viewportWidth() { + return this.orientation == 'horizontal' ? this.viewportSize : this.viewportCrossSize; + } + + get viewportHeight() { + return this.orientation == 'horizontal' ? this.viewportCrossSize : this.viewportSize; + } +} diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk/scrolling/virtual-scroll-viewport.ts similarity index 100% rename from src/cdk-experimental/scrolling/virtual-scroll-viewport.ts rename to src/cdk/scrolling/virtual-scroll-viewport.ts diff --git a/src/demo-app/demo-material-module.ts b/src/demo-app/demo-material-module.ts index 2691a04a68f4..a4e8f5efc9d9 100644 --- a/src/demo-app/demo-material-module.ts +++ b/src/demo-app/demo-material-module.ts @@ -6,18 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {ScrollingModule} from '@angular/cdk-experimental/scrolling'; import {DialogModule} from '@angular/cdk-experimental/dialog'; import {DragDropModule} from '@angular/cdk-experimental/drag-drop'; +import {ScrollingModule as ExperimentalScrollingModule} from '@angular/cdk-experimental/scrolling'; import {A11yModule} from '@angular/cdk/a11y'; import {CdkAccordionModule} from '@angular/cdk/accordion'; import {BidiModule} from '@angular/cdk/bidi'; -import {TextFieldModule} from '@angular/cdk/text-field'; import {ObserversModule} from '@angular/cdk/observers'; import {OverlayModule} from '@angular/cdk/overlay'; import {PlatformModule} from '@angular/cdk/platform'; import {PortalModule} from '@angular/cdk/portal'; +import {ScrollingModule} from '@angular/cdk/scrolling'; import {CdkTableModule} from '@angular/cdk/table'; +import {TextFieldModule} from '@angular/cdk/text-field'; import {CdkTreeModule} from '@angular/cdk/tree'; import {NgModule} from '@angular/core'; import { @@ -112,6 +113,7 @@ import { PlatformModule, PortalModule, ScrollingModule, + ExperimentalScrollingModule, DialogModule, DragDropModule, ] diff --git a/src/lib/sidenav/sidenav-module.ts b/src/lib/sidenav/sidenav-module.ts index 370ef4148e69..f0f98af5d554 100644 --- a/src/lib/sidenav/sidenav-module.ts +++ b/src/lib/sidenav/sidenav-module.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {PlatformModule} from '@angular/cdk/platform'; -import {ScrollDispatchModule} from '@angular/cdk/scrolling'; +import {ScrollingModule} from '@angular/cdk/scrolling'; import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; @@ -18,7 +18,7 @@ import {MatSidenav, MatSidenavContainer, MatSidenavContent} from './sidenav'; imports: [ CommonModule, MatCommonModule, - ScrollDispatchModule, + ScrollingModule, PlatformModule, ], exports: [ diff --git a/src/lib/tabs/tab-header.spec.ts b/src/lib/tabs/tab-header.spec.ts index efd3dfb19afa..3ff61a32310d 100644 --- a/src/lib/tabs/tab-header.spec.ts +++ b/src/lib/tabs/tab-header.spec.ts @@ -1,7 +1,7 @@ import {Direction, Directionality} from '@angular/cdk/bidi'; import {END, ENTER, HOME, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes'; import {PortalModule} from '@angular/cdk/portal'; -import {ScrollDispatchModule, ViewportRuler} from '@angular/cdk/scrolling'; +import {ScrollingModule, ViewportRuler} from '@angular/cdk/scrolling'; import {dispatchFakeEvent, dispatchKeyboardEvent} from '@angular/cdk/testing'; import {CommonModule} from '@angular/common'; import {Component, ViewChild} from '@angular/core'; @@ -30,7 +30,7 @@ describe('MatTabHeader', () => { beforeEach(async(() => { dir = 'ltr'; TestBed.configureTestingModule({ - imports: [CommonModule, PortalModule, MatRippleModule, ScrollDispatchModule], + imports: [CommonModule, PortalModule, MatRippleModule, ScrollingModule], declarations: [ MatTabHeader, MatInkBar, diff --git a/src/material-examples/material-module.ts b/src/material-examples/material-module.ts index a9869d112bc5..cb1386c813f2 100644 --- a/src/material-examples/material-module.ts +++ b/src/material-examples/material-module.ts @@ -1,6 +1,6 @@ import {NgModule} from '@angular/core'; -import {ScrollDispatchModule} from '@angular/cdk/scrolling'; +import {ScrollingModule} from '@angular/cdk/scrolling'; import {A11yModule} from '@angular/cdk/a11y'; import {CdkTableModule} from '@angular/cdk/table'; import {CdkTreeModule} from '@angular/cdk/tree'; @@ -54,7 +54,7 @@ import { MatToolbarModule, MatTooltipModule, MatTreeModule, - ScrollDispatchModule, + ScrollingModule, ], exports: [ A11yModule, @@ -95,7 +95,7 @@ import { MatToolbarModule, MatTooltipModule, MatTreeModule, - ScrollDispatchModule, + ScrollingModule, ] }) export class ExampleMaterialModule {} From 06ce6ca85ed3cfb1933d2a907529703108fc0c25 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Mon, 30 Jul 2018 10:53:52 -0700 Subject: [PATCH 2/3] fix CI issues --- src/cdk-experimental/scrolling/BUILD.bazel | 1 - src/cdk/scrolling/BUILD.bazel | 2 ++ src/cdk/scrolling/index.ts | 2 +- src/e2e-app/e2e-app-module.ts | 4 +++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cdk-experimental/scrolling/BUILD.bazel b/src/cdk-experimental/scrolling/BUILD.bazel index 43bb6c21bde9..1c7399b9d660 100644 --- a/src/cdk-experimental/scrolling/BUILD.bazel +++ b/src/cdk-experimental/scrolling/BUILD.bazel @@ -1,7 +1,6 @@ package(default_visibility=["//visibility:public"]) load("@angular//:index.bzl", "ng_module") load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test") -load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary") ng_module( diff --git a/src/cdk/scrolling/BUILD.bazel b/src/cdk/scrolling/BUILD.bazel index 04b6c898ded5..b22fc55e5b44 100644 --- a/src/cdk/scrolling/BUILD.bazel +++ b/src/cdk/scrolling/BUILD.bazel @@ -1,6 +1,7 @@ package(default_visibility=["//visibility:public"]) load("@angular//:index.bzl", "ng_module") load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test") +load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary") ng_module( @@ -28,6 +29,7 @@ ts_library( srcs = glob(["**/*.spec.ts"]), deps = [ ":scrolling", + "//src/cdk/collections", "//src/cdk/testing", "@rxjs", ], diff --git a/src/cdk/scrolling/index.ts b/src/cdk/scrolling/index.ts index ed7fe9c90d76..90be30cdc9fd 100644 --- a/src/cdk/scrolling/index.ts +++ b/src/cdk/scrolling/index.ts @@ -10,6 +10,6 @@ export * from './public-api'; /** * @deprecated ScrollingModule has been renamed to ScrollingModule. - * @deletion-target 8.0.0 + * @breaking-change 8.0.0 delete this re-export */ export {ScrollingModule as ScrollDispatchModule} from './scrolling-module'; diff --git a/src/e2e-app/e2e-app-module.ts b/src/e2e-app/e2e-app-module.ts index 2d9c4f329b36..2792fbd5a4d5 100644 --- a/src/e2e-app/e2e-app-module.ts +++ b/src/e2e-app/e2e-app-module.ts @@ -1,7 +1,8 @@ -import {ScrollingModule} from '@angular/cdk-experimental/scrolling'; import {DialogModule} from '@angular/cdk-experimental/dialog'; import {DragDropModule} from '@angular/cdk-experimental/drag-drop'; +import {ScrollingModule as ExperimentalScrollingModule} from '@angular/cdk-experimental/scrolling'; import {FullscreenOverlayContainer, OverlayContainer} from '@angular/cdk/overlay'; +import {ScrollingModule} from '@angular/cdk/scrolling'; import {NgModule} from '@angular/core'; import {ReactiveFormsModule} from '@angular/forms'; import { @@ -71,6 +72,7 @@ import {VirtualScrollE2E} from './virtual-scroll/virtual-scroll-e2e'; MatTabsModule, MatNativeDateModule, ScrollingModule, + ExperimentalScrollingModule, DialogModule, DragDropModule, ] From 3fe39619e06b2ddd6df71a24ef9cf1e2b4e90519 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 1 Aug 2018 10:36:22 -0700 Subject: [PATCH 3/3] fix module re-export --- src/cdk/scrolling/index.ts | 6 ------ src/cdk/scrolling/scrolling-module.ts | 10 ++++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cdk/scrolling/index.ts b/src/cdk/scrolling/index.ts index 90be30cdc9fd..676ca90f1ffa 100644 --- a/src/cdk/scrolling/index.ts +++ b/src/cdk/scrolling/index.ts @@ -7,9 +7,3 @@ */ export * from './public-api'; - -/** - * @deprecated ScrollingModule has been renamed to ScrollingModule. - * @breaking-change 8.0.0 delete this re-export - */ -export {ScrollingModule as ScrollDispatchModule} from './scrolling-module'; diff --git a/src/cdk/scrolling/scrolling-module.ts b/src/cdk/scrolling/scrolling-module.ts index ad2282d6d10b..a4d533dfe22c 100644 --- a/src/cdk/scrolling/scrolling-module.ts +++ b/src/cdk/scrolling/scrolling-module.ts @@ -29,3 +29,13 @@ import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; ], }) export class ScrollingModule {} + +/** + * @deprecated ScrollDispatchModule has been renamed to ScrollingModule. + * @breaking-change 8.0.0 delete this alias + */ +@NgModule({ + imports: [ScrollingModule], + exports: [ScrollingModule], +}) +export class ScrollDispatchModule {}