From f042c6a9ca1bd9b7c889822c5b8f565019274ed2 Mon Sep 17 00:00:00 2001 From: Oren Farhi Date: Tue, 26 Sep 2017 13:34:45 +0300 Subject: [PATCH] fix #157 #162 #171 #188, add examples --- README.md | 12 +-- example/bs-config.json | 5 ++ example/config.js | 56 +++++++++++++ example/index.html | 28 +++++++ example/src/app.ts | 80 +++++++++++++++++++ example/src/main.ts | 15 ++++ example/style.css | 14 ++++ package.json | 5 +- src/modules/infinite-scroll.directive.ts | 50 +++++++----- src/modules/ngx-infinite-scroll.module.ts | 4 +- src/services/ngx-ins-utils.ts | 11 +++ src/services/position-resolver.ts | 2 +- .../modules/infinite-scroll.directive.spec.ts | 21 ++++- tsconfig.json | 4 +- tslint.json | 8 -- 15 files changed, 272 insertions(+), 43 deletions(-) create mode 100644 example/bs-config.json create mode 100644 example/config.js create mode 100644 example/index.html create mode 100644 example/src/app.ts create mode 100644 example/src/main.ts create mode 100644 example/style.css create mode 100644 src/services/ngx-ins-utils.ts diff --git a/README.md b/README.md index bf6dfae3..7e8db3c1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [![Build Status](https://travis-ci.org/orizens/ngx-infinite-scroll.svg?branch=master)](https://travis-ci.org/orizens/ngx-infinite-scroll) # Angular Infinite Scroll -Inspired by [ng-infinite-scroll](https://github.com/sroze/ngInfiniteScroll) directive for angular (> 2, 4). +Inspired by [ng-infinite-scroll](https://github.com/sroze/ngInfiniteScroll) directive for angular. ## Angular Support -Supports Angular **> 4** +**Supports Angular > 4** For Angular version **<= 2.3.1**, you can use ```npm i angular2-infinite-scroll``` (latest version is 0.3.42) - please notice **the angular2-infinite-scroll** package is deprecated ## Angular Consulting Services @@ -31,6 +31,8 @@ Currently supported attributes: * **scrollWindow**<_boolean_> - (optional, default: **true**) - listens to the window scroll instead of the actual element scroll. this allows to invoke a callback function in the scope of the element while listenning to the window scroll. * **immediateCheck**<_boolean_> - (optional, default: **false**) - invokes the handler immediately to check if a scroll event has been already triggred when the page has been loaded (i.e. - when you refresh a page that has been scrolled). * **infiniteScrollDisabled**<_boolean_> - (optional, default: **false**) - doesn't invoke the handler if set to true +* **horizontal**<_boolean_> - (optional, default: false) - sets the scroll to listen for horizontal events +* **alwaysCallback**<_boolean_> - (optional, default: false) - instructs the scroller to always trigger events ## Behavior By default, the directive listens to the **window scroll** event and invoked the callback. @@ -73,7 +75,7 @@ import { Component } from '@angular/core';
` @@ -101,7 +103,7 @@ import { Component } from '@angular/core';
@@ -128,7 +130,7 @@ import { InfiniteScroll } from 'ngx-infinite-scroll'; infiniteScroll [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" - [infiniteScrollThrottle]="500" + [infiniteScrollThrottle]="50" (scrolled)="onScrollDown()" (scrolledUp)="onScrollUp()"> diff --git a/example/bs-config.json b/example/bs-config.json new file mode 100644 index 00000000..5a8bc7f4 --- /dev/null +++ b/example/bs-config.json @@ -0,0 +1,5 @@ +{ + "port": 8000, + "files": ["./example**/*.{html,htm,css,js}"], + "server": [ "./example", "./dist/bundles" ] +} \ No newline at end of file diff --git a/example/config.js b/example/config.js new file mode 100644 index 00000000..a86bb589 --- /dev/null +++ b/example/config.js @@ -0,0 +1,56 @@ +let systemConfig = { + //use typescript for compilation + transpiler: 'typescript', + //typescript compiler options + typescriptOptions: { + emitDecoratorMetadata: true + }, + //map tells the System loader where to look for things + map: { + app: './src', + '@angular': 'https://unpkg.com/@angular', + '@angular/common': 'https://unpkg.com/@angular/common@4.2.4', + '@angular/core': 'https://unpkg.com/@angular/core@4.2.4', + '@angular/compiler': 'https://unpkg.com/@angular/compiler@4.2.4', + '@angular/platform-browser-dynamic': 'https://unpkg.com/@angular/platform-browser-dynamic@4.2.4', + '@angular/platform-browser': 'https://unpkg.com/@angular/platform-browser@4.2.4', + 'rxjs': 'https://unpkg.com/rxjs@5.4.2' + // 'ngx-infinite-scroll': 'https://unpkg.com/ngx-infinite-scroll' + }, + //packages defines our app package + packages: { + app: { + main: './main.ts', + defaultExtension: 'ts' + }, + '@angular/core': { + main: 'bundles/core.umd.js', + defaultExtension: 'js' + }, + '@angular/compiler': { + main: 'bundles/compiler.umd.js', + defaultExtension: 'js' + }, + '@angular/common': { + main: 'bundles/common.umd.js', + defaultExtension: 'js' + }, + '@angular/platform-browser-dynamic': { + main: 'bundles/platform-browser-dynamic.umd.js', + defaultExtension: 'js' + }, + '@angular/platform-browser': { + main: 'bundles/platform-browser.umd.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ngx-infinite-scroll': { + main: '../ngx-infinite-scroll.umd.js', + defaultExtension: 'js' + } + } +}; +System.config(systemConfig); +module.exports = systemConfig; \ No newline at end of file diff --git a/example/index.html b/example/index.html new file mode 100644 index 00000000..12c8faf7 --- /dev/null +++ b/example/index.html @@ -0,0 +1,28 @@ + + + + + Angular Playground + + + + + + + + + + + + + + + + loading... + + + + diff --git a/example/src/app.ts b/example/src/app.ts new file mode 100644 index 00000000..cb20e81d --- /dev/null +++ b/example/src/app.ts @@ -0,0 +1,80 @@ +// our root app component +import { Component, ViewEncapsulation } from '@angular/core'; +import * as config from '../config'; + +@Component({ + selector: 'my-app', + styleUrls: [ './style.css' ], + template: ` +

+ {{ title }} +
+ items: {{sum}}, now triggering scroll: {{direction}} +
+

+
+

+ {{ i }} +

+
+ ` +}) +export class AppComponent { + array = []; + sum = 100; + throttle = 300; + scrollDistance = 1; + scrollUpDistance = 2; + direction = ''; + // title = 'This is Angular InfiniteScroll v' + systemConfig.map['ngx-infinite-scroll'].split('@')[1]; + + constructor() { + this.appendItems(0, this.sum); + // debugger + // console.log(systemConfig); + } + + addItems(startIndex, endIndex, _method) { + for (let i = 0; i < this.sum; ++i) { + this.array[_method]([i, ' ', this.generateWord()].join('')); + } + } + + appendItems(startIndex, endIndex) { + this.addItems(startIndex, endIndex, 'push'); + } + + prependItems(startIndex, endIndex) { + this.addItems(startIndex, endIndex, 'unshift'); + } + + onScrollDown (ev) { + console.log('scrolled down!!', ev); + + // add another 20 items + const start = this.sum; + this.sum += 20; + this.appendItems(start, this.sum); + + this.direction = 'down'; + } + + onUp(ev) { + console.log('scrolled up!', ev); + const start = this.sum; + this.sum += 20; + this.prependItems(start, this.sum); + + this.direction = 'up'; + } + generateWord() { + return chance.word(); + } +} \ No newline at end of file diff --git a/example/src/main.ts b/example/src/main.ts new file mode 100644 index 00000000..c1add5a1 --- /dev/null +++ b/example/src/main.ts @@ -0,0 +1,15 @@ +// main entry point +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { InfiniteScrollModule } from 'ngx-infinite-scroll'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppComponent } from './app'; + +@NgModule({ + declarations: [ AppComponent ], + imports: [ BrowserModule, InfiniteScrollModule ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/example/style.css b/example/style.css new file mode 100644 index 00000000..9b240ee1 --- /dev/null +++ b/example/style.css @@ -0,0 +1,14 @@ +.search-results { + height: 100%; +} +.title { + position: fixed; + top: 0; + left: 0; + background-color: rgba(0,0,0,.5); + color: white; + width: 100%; +} +.title small { + color: #eaeaea; +} \ No newline at end of file diff --git a/package.json b/package.json index 210d37fe..818721b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngx-infinite-scroll", - "version": "0.5.1", + "version": "0.5.2", "description": "An infinite scroll directive for Angular compatible with AoT compilation and Tree shaking", "main": "./bundles/ngx-infinite-scroll.umd.js", "module": "./modules/ngx-infinite-scroll.es5.js", @@ -13,7 +13,8 @@ "publish:beta": "npm publish --tag beta ./dist", "compodoc": "compodoc -p tsconfig.json", "compodoc-serve": "compodoc -s", - "transpile": "ngc -p ./tsconfig.json" + "transpile": "ngc -p ./tsconfig.json", + "serve:prod": "npm run build && lite-server -c ./example/bs-config.json" }, "typings": "./ngx-infinite-scroll.d.ts", "author": "Oren Farhi (orizens.com)", diff --git a/src/modules/infinite-scroll.directive.ts b/src/modules/infinite-scroll.directive.ts index 9c674eba..cb5eb07f 100644 --- a/src/modules/infinite-scroll.directive.ts +++ b/src/modules/infinite-scroll.directive.ts @@ -1,20 +1,29 @@ import { InfiniteScrollEvent, IScrollStats, IPositionStats, IResolver } from '../models'; import { - Directive, ElementRef, Input, Output, - EventEmitter, OnDestroy, OnInit, - SimpleChanges, NgZone + Directive, + ElementRef, + EventEmitter, + Input, + NgZone, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, } from '@angular/core'; import { PositionResolver } from '../services/position-resolver'; import { ScrollRegister, IScrollRegisterConfig } from '../services/scroll-register'; import { ScrollResolver } from '../services/scroll-resolver'; import { AxisResolver } from '../services/axis-resolver'; +import { resolveContainerElement } from '../services/ngx-ins-utils'; import { Subscription } from 'rxjs/Subscription'; @Directive({ - selector: '[infiniteScroll], [infinite-scroll], [data-infinite-scroll]' + providers: [ScrollResolver], + selector: '[infiniteScroll], [infinite-scroll], [data-infinite-scroll]', }) -export class InfiniteScrollDirective implements OnDestroy, OnInit { +export class InfiniteScrollDirective implements OnDestroy, OnInit, OnChanges { @Output() scrolled = new EventEmitter(); @Output() scrolledUp = new EventEmitter(); @@ -39,9 +48,21 @@ export class InfiniteScrollDirective implements OnDestroy, OnInit { ) {} ngOnInit() { + this.setup(); + } + + ngOnChanges({ infiniteScrollContainer }: SimpleChanges) { + const scrollContainerChanged = infiniteScrollContainer && !infiniteScrollContainer.firstChange; + if (scrollContainerChanged) { + this.destroyScroller(); + this.setup(); + } + } + + setup() { if (typeof window !== 'undefined') { this.zone.runOutsideAngular(() => { - const containerElement = this.resolveContainerElement(); + const containerElement = resolveContainerElement(this.infiniteScrollContainer, this.scrollWindow, this.element); const resolver = this.positionResolver.create({ axis: new AxisResolver(!this.horizontal), windowElement: containerElement, @@ -81,9 +102,7 @@ export class InfiniteScrollDirective implements OnDestroy, OnInit { } ngOnDestroy () { - if (this.disposeScroller) { - this.disposeScroller.unsubscribe(); - } + this.destroyScroller(); } onScrollDown(data: InfiniteScrollEvent = { currentScrollPosition: 0 }) { @@ -94,16 +113,9 @@ export class InfiniteScrollDirective implements OnDestroy, OnInit { this.zone.run(() => this.scrolledUp.emit(data)); } - private resolveContainerElement(): any { - const selector = this.infiniteScrollContainer; - const hasWindow = window && window.hasOwnProperty('document'); - const containerIsString = selector && hasWindow && typeof(this.infiniteScrollContainer) === 'string'; - let container = containerIsString - ? window.document.querySelector(selector) - : selector; - if (!selector) { - container = this.scrollWindow ? window : this.element; + destroyScroller() { + if (this.disposeScroller) { + this.disposeScroller.unsubscribe(); } - return container; } } diff --git a/src/modules/ngx-infinite-scroll.module.ts b/src/modules/ngx-infinite-scroll.module.ts index 39680259..6dfe26b2 100644 --- a/src/modules/ngx-infinite-scroll.module.ts +++ b/src/modules/ngx-infinite-scroll.module.ts @@ -3,7 +3,6 @@ import { NgModule } from '@angular/core'; import { InfiniteScrollDirective } from './infinite-scroll.directive'; import { PositionResolver } from '../services/position-resolver'; import { ScrollRegister } from '../services/scroll-register'; -import { ScrollResolver } from '../services/scroll-resolver'; @NgModule({ declarations: [InfiniteScrollDirective], @@ -11,8 +10,7 @@ import { ScrollResolver } from '../services/scroll-resolver'; imports: [], providers: [ PositionResolver, - ScrollRegister, - ScrollResolver + ScrollRegister ] }) export class InfiniteScrollModule { } diff --git a/src/services/ngx-ins-utils.ts b/src/services/ngx-ins-utils.ts new file mode 100644 index 00000000..3f369738 --- /dev/null +++ b/src/services/ngx-ins-utils.ts @@ -0,0 +1,11 @@ +export function resolveContainerElement(selector: string | any, scrollWindow, defaultElement): any { + const hasWindow = window && window.hasOwnProperty('document'); + const containerIsString = selector && hasWindow && typeof(selector) === 'string'; + let container = containerIsString + ? window.document.querySelector(selector) + : selector; + if (!selector) { + container = scrollWindow ? window : defaultElement; + } + return container; +} diff --git a/src/services/position-resolver.ts b/src/services/position-resolver.ts index c18c9b66..884a0692 100644 --- a/src/services/position-resolver.ts +++ b/src/services/position-resolver.ts @@ -23,7 +23,7 @@ export class PositionResolver { } isElementWindow(windowElement: ContainerRef): boolean { - const isWindow = Object.prototype.toString.call(windowElement).includes('Window'); + const isWindow = ['Window', 'global'].some((obj) => Object.prototype.toString.call(windowElement).includes(obj)); return isWindow; } diff --git a/tests/modules/infinite-scroll.directive.spec.ts b/tests/modules/infinite-scroll.directive.spec.ts index 4ac5e94e..447ed32a 100644 --- a/tests/modules/infinite-scroll.directive.spec.ts +++ b/tests/modules/infinite-scroll.directive.spec.ts @@ -39,7 +39,7 @@ describe('Infinite Scroll Directive', () => { return { shouldScroll: true, isScrollingDown }; } }; - scrollRegisterSpy = jasmine.createSpyObj('register', ['attachEvent']) + scrollRegisterSpy = jasmine.createSpyObj('register', ['attachEvent']); positionResolverSpy = jasmine.createSpyObj('pos', ['create', 'container']); directive = createInfiniteScroll(); }); @@ -70,10 +70,10 @@ describe('Infinite Scroll Directive', () => { height: 0, scrolledUntilNow: 0, totalToScroll: 0, - } + }; spyOn(directive, 'onScrollDown'); directive.ngOnInit(); - directive.handleOnScroll(container) + directive.handleOnScroll(container); const actual = directive.onScrollDown; expect(actual).toHaveBeenCalled(); }); @@ -97,7 +97,7 @@ describe('Infinite Scroll Directive', () => { height: 0, scrolledUntilNow: 0, totalToScroll: 0, - } + }; spyOn(directive, 'onScrollDown'); directive.ngOnInit(); directive.infiniteScrollDisabled = true; @@ -175,6 +175,19 @@ describe('Infinite Scroll Directive', () => { expect(positionFactoryMock.create) .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: mockedElement})); }); + + it('should initialize again when infiniteScrollContainer has changed', () => { + const anotherContainer = { + height: 0, + scrolledUntilNow: 0, + totalToScroll: 0, + }; + directive.infiniteScrollContainer = anotherContainer; + const change = new SimpleChange(container, anotherContainer, false); + directive.ngOnChanges({ infiniteScrollContainer: change }); + expect(positionFactoryMock.create) + .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: anotherContainer})); + }); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index dfd43e3e..052aa56d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,8 @@ ] }, "exclude": [ - "node_modules" + "node_modules", + "example", + "dist" ] } \ No newline at end of file diff --git a/tslint.json b/tslint.json index 46d25cdb..454a8e42 100644 --- a/tslint.json +++ b/tslint.json @@ -7,19 +7,11 @@ "directive-selector": [ true, "attribute", - [ - "dir-prefix1", - "dir-prefix2" - ], "camelCase" ], "component-selector": [ true, "element", - [ - "cmp-prefix1", - "cmp-prefix2" - ], "kebab-case" ], "use-input-property-decorator": true,