diff --git a/demo/app/samples/test.component.html b/demo/app/samples/test.component.html index b4b0a82d..29dc3bd2 100644 --- a/demo/app/samples/test.component.html +++ b/demo/app/samples/test.component.html @@ -22,7 +22,7 @@
- ngx-ui-scroll version: {{datasource.adapter.version}} + ngx-ui-scroll version: {{datasource.adapter.packageInfo.consumer.version}}
isLoading: {{datasource.adapter.isLoading}} @@ -47,14 +47,9 @@

-
-
+
{{item.text}} @@ -63,4 +58,4 @@
-
+
\ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bbf19b23..7d33f964 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "ngx-bootstrap": "^8.0.0-RC.5", "rxjs": "~7.5.1", "tslib": "^2.3.1", - "vscroll": "^1.5.4", + "vscroll": "^1.5.5", "zone.js": "~0.11.4" }, "devDependencies": { @@ -12846,9 +12846,9 @@ } }, "node_modules/vscroll": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/vscroll/-/vscroll-1.5.4.tgz", - "integrity": "sha512-DPXeIJ9PN5SapeasmmYCTGpGNh1Uwjxr3f00zS2pbG1YbXcvKFyTJj4/7PP1b/ihzhqABA+GG+qPAdCA703Grw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/vscroll/-/vscroll-1.5.5.tgz", + "integrity": "sha512-zm7YbxLYH3sp8WXzH0VIiTsu79N3XnYhLCFjAlkslEGxMfKUGI7b7CKMGM4CDbM251qFMqfXSSr82X8U9rqcoA==", "dependencies": { "tslib": "^2.3.1" } @@ -22743,9 +22743,9 @@ "dev": true }, "vscroll": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/vscroll/-/vscroll-1.5.4.tgz", - "integrity": "sha512-DPXeIJ9PN5SapeasmmYCTGpGNh1Uwjxr3f00zS2pbG1YbXcvKFyTJj4/7PP1b/ihzhqABA+GG+qPAdCA703Grw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/vscroll/-/vscroll-1.5.5.tgz", + "integrity": "sha512-zm7YbxLYH3sp8WXzH0VIiTsu79N3XnYhLCFjAlkslEGxMfKUGI7b7CKMGM4CDbM251qFMqfXSSr82X8U9rqcoA==", "requires": { "tslib": "^2.3.1" } diff --git a/package.json b/package.json index 17d78552..fe3daa25 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "ngx-bootstrap": "^8.0.0-RC.5", "rxjs": "~7.5.1", "tslib": "^2.3.1", - "vscroll": "^1.5.4", + "vscroll": "^1.5.5", "zone.js": "~0.11.4" }, "devDependencies": { @@ -56,4 +56,4 @@ "puppeteer": "^13.0.1", "typescript": "~4.5.4" } -} +} \ No newline at end of file diff --git a/scroller/package.json b/scroller/package.json index caa8eb0c..c50052bb 100644 --- a/scroller/package.json +++ b/scroller/package.json @@ -1,13 +1,10 @@ { "name": "ngx-ui-scroll", - "version": "3.0.2", + "version": "3.1.0", "license": "MIT", "peerDependencies": { "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", - "vscroll": "^1.5.4" - }, - "dependencies": { - "tslib": "^2.3.1" + "vscroll": "^1.5.5" } } \ No newline at end of file diff --git a/scroller/src/ui-scroll.component.ts b/scroller/src/ui-scroll.component.ts index a613b75f..b1350cea 100644 --- a/scroller/src/ui-scroll.component.ts +++ b/scroller/src/ui-scroll.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, OnDestroy, TemplateRef, ElementRef, - ChangeDetectionStrategy, ChangeDetectorRef + ChangeDetectionStrategy, ChangeDetectorRef, NgZone } from '@angular/core'; import { IDatasource, Workflow, Item } from './vscroll'; @@ -43,25 +43,43 @@ export class UiScrollComponent implements OnInit, OnDestroy { public workflow!: Workflow; constructor( - public changeDetector: ChangeDetectorRef, - public elementRef: ElementRef) { } + private changeDetector: ChangeDetectorRef, + private elementRef: ElementRef, + private ngZone: NgZone + ) {} ngOnInit(): void { - this.workflow = new Workflow({ - consumer, - element: this.elementRef.nativeElement, - datasource: this.datasource as IDatasource, - run: (items: Item[]) => { - if (!items.length && !this.items.length) { - return; - } - this.items = items; - this.changeDetector.detectChanges(); - } - }); + // The workflow should be created outside of the Angular zone because it's causing many + // change detection cycles. It runs its `init()` function in a `setTimeout` task, which + // then sets up the `scroll` listener. The `scroll` event listener would force Angular to + // run `tick()` any time the `scroll` task is invoked. We don't care about `scroll` events + // since they're handled internally by `vscroll`. We still run change detection manually + // tho when the `run` function is invoked. + // `scroll` events may be also unpatched through zone.js flags: + // `(window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll']`. + // Having `runOutsideAngular` guarantees we're responsible for running change detection + // only when it's "required" (when `items` are updated and the template should be updated too). + this.workflow = this.ngZone.runOutsideAngular( + () => + new Workflow({ + consumer, + element: this.elementRef.nativeElement, + datasource: this.datasource as IDatasource, + run: (items: Item[]) => { + if (!items.length && !this.items.length) { + return; + } + // Re-enter the Angular zone only when items are set and when we have to run the local change detection. + this.ngZone.run(() => { + this.items = items; + this.changeDetector.detectChanges(); + }); + }, + }) + ); } ngOnDestroy(): void { - this.workflow && this.workflow.dispose(); + this.workflow?.dispose(); } } diff --git a/scroller/src/ui-scroll.directive.ts b/scroller/src/ui-scroll.directive.ts index 1c504fcd..dce23db0 100644 --- a/scroller/src/ui-scroll.directive.ts +++ b/scroller/src/ui-scroll.directive.ts @@ -1,4 +1,10 @@ -import { Directive, Input, TemplateRef, ViewContainerRef, ComponentFactoryResolver, OnInit } from '@angular/core'; +import { + Directive, + Input, + TemplateRef, + ViewContainerRef, + OnInit, +} from '@angular/core'; import { UiScrollComponent } from './ui-scroll.component'; import { IDatasource } from './ui-scroll.datasource'; @@ -9,20 +15,15 @@ export class UiScrollDirective implements OnInit { constructor( private templateRef: TemplateRef, - private viewContainer: ViewContainerRef, - private resolver: ComponentFactoryResolver - ) { - } + private viewContainer: ViewContainerRef + ) {} @Input() set uiScrollOf(datasource: IDatasource) { this.datasource = datasource; } ngOnInit(): void { - const compFactory = this.resolver.resolveComponentFactory(UiScrollComponent); - const componentRef = this.viewContainer.createComponent( - compFactory, void 0, this.viewContainer.injector - ); + const componentRef = this.viewContainer.createComponent(UiScrollComponent); componentRef.instance.datasource = this.datasource as IDatasource; componentRef.instance.template = this.templateRef; } diff --git a/scroller/src/ui-scroll.module.ts b/scroller/src/ui-scroll.module.ts index 094d0185..a72f80fb 100644 --- a/scroller/src/ui-scroll.module.ts +++ b/scroller/src/ui-scroll.module.ts @@ -12,6 +12,5 @@ import { UiScrollDirective } from './ui-scroll.directive'; imports: [CommonModule], entryComponents: [UiScrollComponent], exports: [UiScrollDirective], - providers: [] }) export class UiScrollModule { } diff --git a/scroller/src/ui-scroll.version.ts b/scroller/src/ui-scroll.version.ts index 497fa0b4..c20c9123 100644 --- a/scroller/src/ui-scroll.version.ts +++ b/scroller/src/ui-scroll.version.ts @@ -1,4 +1,4 @@ export default { name: 'ngx-ui-scroll', - version: '3.0.2' + version: '3.1.0' }; diff --git a/tests/recreation.spec.ts b/tests/recreation.spec.ts index bc35ed89..a3e8e8fb 100644 --- a/tests/recreation.spec.ts +++ b/tests/recreation.spec.ts @@ -38,18 +38,25 @@ describe('Recreation Spec', () => { adapterId = misc.adapter.id; }); - const init = (flag: boolean): Promise => - firstValueFrom( - misc.testComponent.datasource.adapter.init$.pipe( - filter(v => flag ? v : !v), take(1) - )); + const init = (flag: boolean): boolean | Promise => + (flag !== misc.testComponent.datasource.adapter.init) ? + firstValueFrom( + misc.testComponent.datasource.adapter.init$.pipe( + filter(v => flag ? v : !v), take(1) + )) + : Promise.resolve(flag); const ngIfReload = async (onBeforeHide?: () => void): Promise => { await init(true); await misc.adapter.relax(); + + // hide test component onBeforeHide && onBeforeHide(); misc.testComponent.show = false; + misc.fixture.changeDetectorRef.detectChanges(); await init(false); + + // show test component misc.testComponent.show = true; misc.fixture.changeDetectorRef.detectChanges(); await init(true);