diff --git a/.gitignore b/.gitignore
index 9f5e8a18..d8c2af69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,4 +50,11 @@ typings
src/ngFactory
*.metadata.json
-dist
\ No newline at end of file
+
+$ cat .gitignore
+/node_modules
+/dist
+/documentation
+
+*.log
+*.tgz
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index ac1c2de6..0430783b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,23 @@
+sudo: required
+dist: trusty
+addons:
+ apt:
+ sources:
+ - google-chrome
+ packages:
+ - google-chrome-stable
language: node_js
-sudo: false
node_js:
-- '5.9.1'
-
+ - stable
+before_install:
+ - npm i npm@^4 -g
install:
-- npm install
-
-after_script:
-- process.exit()
-
-cache:
- directories:
- - $HOME/.nvm
\ No newline at end of file
+ - npm install
+script:
+ - npm test
+before_script:
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+ - sleep 3
+notifications:
+ email: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 49e8a5b2..ca58b9ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,27 +1,13 @@
-## v 0.3.3 (2017/03/01)
+## Angular library starter Changelog
-### Updates
-* reverted the fix of #126
+
+### Mar 25, 2017
+* Upgrade to Angular 4 configuration
-## v 0.3.2 (2017/03/01)
-
-### Updates
-* fixes [#126](https://github.com/orizens/angular2-infinite-scroll/issues/126)
-
-## v 0.3.1 (2017/02/15)
-
-### Updates
-* added custom scrollable container from [#108](https://github.com/orizens/angular2-infinite-scroll/pull/108/files)
-
-## v 0.3.0 (2017/01/31)
-
-### Updates
-* refactored infinite scroller to smaller modules with composition
-* added "models" - includes interfaces for development
-
-## v 0.2.9 (2017/01/13)
-
-### Updates
-* added changelog.md
-* ([refactor(scroll): replaces throttle with debounce](https://github.com/orizens/angular2-infinite-scroll/pull/82))
+
+### Mar 6, 2017
+* Add _compodoc_ for generating documentation
+
+### Feb 5, 2017
+* Create library
\ No newline at end of file
diff --git a/Development Instructions.md b/Development Instructions.md
new file mode 100644
index 00000000..ad076f40
--- /dev/null
+++ b/Development Instructions.md
@@ -0,0 +1,161 @@
+# angular-library-starter
+[![Build Status](https://travis-ci.org/robisim74/angular-library-starter.svg?branch=master)](https://travis-ci.org/robisim74/angular-library-starter)
+>Build an Angular library compatible with AoT compilation & Tree shaking.
+
+This starter allows you to create a library for **Angular 2+** apps written in _TypeScript_, _ES6_ or _ES5_.
+The project is based on the official _Angular_ packages.
+
+Get the [Changelog](https://github.com/robisim74/angular-library-starter/blob/master/CHANGELOG.md).
+
+## Contents
+* [1 Project structure](#1)
+* [2 Customizing](#2)
+* [3 Unit testing](#3)
+* [4 Building](#4)
+* [5 Publishing](#5)
+* [6 Documentation](#6)
+* [7 Using the library](#7)
+* [8 What it is important to know](#8)
+
+## 1 Project structure
+- Library:
+ - **src** folder for the classes
+ - **public_api.ts** entry point for all public APIs of the package
+ - **package.json** _npm_ options
+ - **rollup.config.js** _Rollup_ configuration for building the bundles
+ - **tsconfig-build.json** _ngc_ compiler options for _AoT compilation_
+ - **build.js** building process using _ShellJS_
+- Unit testing:
+ - **tests** folder for unit tests
+ - **karma.conf.js** _Karma_ configuration that uses _webpack_ to build the tests
+ - **spec.bundle.js** defines the files used by _webpack_
+ - **tsconfig.json** _TypeScript_ compiler options
+- Extra:
+ - **tslint.json** _TypeScript_ linter rules with _Codelyzer_
+ - **travis.yml** _Travis CI_ configuration
+
+## 2 Customizing
+1. Update [Node & npm](https://docs.npmjs.com/getting-started/installing-node).
+
+2. Rename `angular-library-starter` and `angularLibraryStarter` everywhere to `my-library` and `myLibrary`.
+
+3. Update in `package.json` file:
+ - version: [Semantic Versioning](http://semver.org/)
+ - description
+ - urls
+ - packages
+
+ and run `npm install`.
+
+4. Create your classes in `src` folder, and export public classes in `my-library.ts`.
+
+5. You can create only one _module_ for the whole library:
+I suggest you create different _modules_ for different functions,
+so that the user can import only those he needs and optimize _Tree shaking_ of his app.
+
+6. Update in `rollup.config.js` file `external` & `globals` libraries with those that actually you use.
+
+7. Create unit tests in `tests` folder.
+_Karma_ is configured to use _webpack_ only for `*.ts` files: if you need to test different formats, you have to update it.
+
+## 3 Unit testing
+```Shell
+npm test
+```
+
+## 4 Building
+The following command:
+```Shell
+npm run build
+```
+- starts _TSLint_ with _Codelyzer_
+- starts _AoT compilation_ using _ngc_ compiler
+- creates `dist` folder with all the files of distribution
+
+To test locally the npm package:
+```Shell
+npm run pack-lib
+```
+Then you can install it in an app to test it:
+```Shell
+npm install [path]my-library-[version].tgz
+```
+
+## 5 Publishing
+Before publishing the first time:
+- you can register your library on [Travis CI](https://travis-ci.org/): you have already configured `.travis.yml` file
+- you must have a user on the _npm_ registry: [Publishing npm packages](https://docs.npmjs.com/getting-started/publishing-npm-packages)
+
+```Shell
+npm run publish-lib
+```
+
+## 6 Documentation
+To generate the documentation, this starter uses [compodoc](https://github.com/compodoc/compodoc):
+```Shell
+npm run compodoc
+npm run compodoc-serve
+```
+
+## 7 Using the library
+### Installing
+```Shell
+npm install my-library --save
+```
+### Loading
+#### Using SystemJS configuration
+```JavaScript
+System.config({
+ map: {
+ 'my-library': 'node_modules/my-library/bundles/my-library.umd.js'
+ }
+});
+```
+#### Angular-CLI
+No need to set up anything, just import it in your code.
+#### Rollup or webpack
+No need to set up anything, just import it in your code.
+#### Plain JavaScript
+Include the `umd` bundle in your `index.html`:
+```Html
+
+```
+and use global `ng.myLibrary` namespace.
+
+### AoT compilation
+The library is compatible with _AoT compilation_.
+
+## 8 What it is important to know
+1. `package.json`
+
+ * `"main": "./bundles/angular-library-starter.umd.js"` legacy module format
+ * `"module": "./bundles/angular-library-starter.es5.js"` flat _ES_ module, for using module bundlers such as _Rollup_ or _webpack_:
+ [package module](https://github.com/rollup/rollup/wiki/pkg.module)
+ * `"es2015": "./bundles/angular-library-starter.js"` _ES2015_ flat _ESM_ format, experimental _ES2015_ build
+ * `"peerDependencies"` the packages and their versions required by the library when it will be installed
+
+2. `tsconfig-build.json` file used by _ngc_ compiler
+
+ * Compiler options:
+ * `"declaration": true` to emit _TypeScript_ declaration files
+ * `"module": "es2015"` & `"target": "es2015"` are used by _Rollup_ to create the _ES2015_ bundle
+
+ * Angular Compiler Options:
+ * `"skipTemplateCodegen": true,` skips generating _AoT_ files
+ * `"annotateForClosureCompiler": true` for compatibility with _Google Closure compiler_
+ * `"strictMetadataEmit": true` without emitting metadata files, the library will not compatible with _AoT compilation_
+
+3. `rollup.config.js` file used by _Rollup_
+
+ * `format: 'umd'` the _Universal Module Definition_ pattern is used by _Angular_ for its bundles
+ * `moduleName: 'ng.angularLibraryStarter'` defines the global namespace used by _JavaScript_ apps
+ * `external` & `globals` declare the external packages
+
+4. Server-side prerendering
+
+ If you want the library will be compatible with server-side prerendering:
+ * `window`, `document`, `navigator` and other browser types do not exist on the server
+ * don't manipulate the _nativeElement_ directly
+
+## License
+MIT
diff --git a/LICENSE b/LICENSE
index 5ef2594b..091d4ab6 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2016 Oren Farhi
+Copyright (c) 2017 Roberto Simonetti
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 10423710..adcfea9d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Inspired by [ng-infinite-scroll](https://github.com/sroze/ngInfiniteScroll) directive for angular (> 2).
## Angular Support
-Supports in Angular - **Final - 2.x.x**
+Supports in Angular **> +2, 4**
## Angular Consulting Services
I'm a Senior Javascript Engineer & A Front End Consultant at [Orizens](http://orizens.com).
@@ -144,15 +144,5 @@ export class AppComponent {
}
```
-## Testing
-To start developing tdd/bdd style: ```npm run dev```
-This will: compile ts files, watch for changes and start the test task. Whenever a ts file is changed, it will rerun the tests.
-
-Travis-ci is integrated
-
-### Credits For Tests Setup
-[ng2-test-seed](https://github.com/juliemr/ng2-test-seed) has been a huge help and source of inspiration. At first, copy & paste, then, customisation to adapt to this code repository.
-Thanks [@juliemr](https://github.com/juliemr)!
-
# Showcase Examples
* [Echoes Player Ng2 Version](http://orizens.github.io/echoes-ng2) ([github repo for echoes player](http://github.com/orizens/echoes-ng2))
diff --git a/build.js b/build.js
new file mode 100644
index 00000000..b9f5c276
--- /dev/null
+++ b/build.js
@@ -0,0 +1,61 @@
+"use strict";
+
+require('shelljs/global');
+const chalk = require('chalk');
+
+const PACKAGE = `ngx-infinite-scroll`;
+const NPM_DIR = `dist`;
+const MODULES_DIR = `${NPM_DIR}/modules`;
+const BUNDLES_DIR = `${NPM_DIR}/bundles`;
+
+echo('Start building...');
+
+rm(`-Rf`, `${NPM_DIR}/*`);
+mkdir(`-p`, `./${MODULES_DIR}`);
+mkdir(`-p`, `./${BUNDLES_DIR}`);
+
+/* TSLint with Codelyzer */
+// https://github.com/palantir/tslint/blob/master/src/configs/recommended.ts
+// https://github.com/mgechev/codelyzer
+echo(`Start TSLint`);
+exec(`tslint --project ./tsconfig.json --type-check ./src/**/*.ts`);
+echo(chalk.green(`TSLint completed`));
+
+/* Aot compilation: ES2015 sources */
+echo(`Start AoT compilation`);
+exec(`ngc -p tsconfig-build.json`);
+echo(chalk.green(`AoT compilation completed`));
+
+/* Creates bundles: ESM/ES5 and UMD bundles */
+echo(`Start bundling`);
+echo(`Rollup package`);
+exec(`rollup -i ${NPM_DIR}/index.js -o ${MODULES_DIR}/${PACKAGE}.js --sourcemap`, {silent: true});
+exec(`node scripts/map-sources -f ${MODULES_DIR}/${PACKAGE}.js`);
+
+echo(`Downleveling ES2015 to ESM/ES5`);
+cp(`${MODULES_DIR}/${PACKAGE}.js`, `${MODULES_DIR}/${PACKAGE}.es5.ts`);
+exec(`tsc ${MODULES_DIR}/${PACKAGE}.es5.ts --target es5 --module es2015 --noLib --sourceMap`, {silent: true});
+exec(`node scripts/map-sources -f ${MODULES_DIR}/${PACKAGE}.es5.js`);
+rm(`-f`, `${MODULES_DIR}/${PACKAGE}.es5.ts`);
+
+echo(`Run Rollup conversion on package`);
+exec(`rollup -c rollup.config.js --sourcemap`, {silent: true});
+exec(`node scripts/map-sources -f ${BUNDLES_DIR}/${PACKAGE}.umd.js`);
+
+echo(`Minifying`);
+cd(`${BUNDLES_DIR}`);
+exec(`uglifyjs -c --screw-ie8 --comments -o ${PACKAGE}.umd.min.js --source-map ${PACKAGE}.umd.min.js.map --source-map-include-sources ${PACKAGE}.umd.js`, {silent: true});
+exec(`node ../../scripts/map-sources -f ${PACKAGE}.umd.min.js`);
+cd(`..`);
+cd(`..`);
+
+echo(chalk.green(`Bundling completed`));
+
+rm(`-Rf`, `${NPM_DIR}/*.js`);
+rm(`-Rf`, `${NPM_DIR}/*.js.map`);
+rm(`-Rf`, `${NPM_DIR}/src/**/*.js`);
+rm(`-Rf`, `${NPM_DIR}/src/**/*.js.map`);
+
+cp(`-Rf`, [`package.json`, `LICENSE`, `README.md`], `${NPM_DIR}`);
+
+echo(chalk.green(`End building`));
diff --git a/bundles/angular2-infinite-scroll.js b/bundles/angular2-infinite-scroll.js
deleted file mode 100644
index f9729f7d..00000000
--- a/bundles/angular2-infinite-scroll.js
+++ /dev/null
@@ -1,428 +0,0 @@
-System.registerDynamic('src/infinite-scroll', ['@angular/core', './position-resolver', './scroll-register', './scroll-resolver'], true, function ($__require, exports, module) {
- "use strict";
-
- var global = this || self,
- GLOBAL = global;
- var core_1 = $__require('@angular/core');
- var position_resolver_1 = $__require('./position-resolver');
- var scroll_register_1 = $__require('./scroll-register');
- var scroll_resolver_1 = $__require('./scroll-resolver');
- var InfiniteScroll = function () {
- function InfiniteScroll(element, zone, positionResolverFactory, scrollRegister, scrollerResolver) {
- this.element = element;
- this.zone = zone;
- this.positionResolverFactory = positionResolverFactory;
- this.scrollRegister = scrollRegister;
- this.scrollerResolver = scrollerResolver;
- this.scrolled = new core_1.EventEmitter();
- this.scrolledUp = new core_1.EventEmitter();
- this._distanceDown = 2;
- this._distanceUp = 1.5;
- this._throttle = 300;
- this._disabled = false;
- this._container = null;
- this.scrollWindow = true;
- this._immediate = false;
- this._horizontal = false;
- this._alwaysCallback = false;
- this.throttleType = 'throttle';
- }
- Object.defineProperty(InfiniteScroll.prototype, "debounce", {
- set: function (value) {
- this.throttleType = value === '' || !!value ? 'debounce' : 'throttle';
- },
- enumerable: true,
- configurable: true
- });
- InfiniteScroll.prototype.ngOnInit = function () {
- var _this = this;
- if (typeof window !== 'undefined') {
- var containerElement = this.resolveContainerElement();
- var positionResolver_1 = this.positionResolverFactory.create({
- windowElement: containerElement,
- horizontal: this._horizontal
- });
- var options = {
- container: positionResolver_1.container,
- throttleType: this.throttleType,
- throttleDuration: this._throttle,
- filterBefore: function () {
- return !_this._disabled;
- },
- mergeMap: function () {
- return positionResolver_1.calculatePoints(_this.element);
- },
- scrollHandler: function (container) {
- return _this.handleOnScroll(container);
- }
- };
- this.disposeScroller = this.scrollRegister.attachEvent(options);
- }
- };
- InfiniteScroll.prototype.handleOnScroll = function (container) {
- var scrollResolverConfig = {
- distance: {
- down: this._distanceDown,
- up: this._distanceUp
- }
- };
- var scrollStats = this.scrollerResolver.getScrollStats(container, scrollResolverConfig);
- if (this.shouldTriggerEvents(scrollStats.shouldScroll)) {
- var infiniteScrollEvent = {
- currentScrollPosition: container.scrolledUntilNow
- };
- if (scrollStats.isScrollingDown) {
- this.onScrollDown(infiniteScrollEvent);
- } else {
- this.onScrollUp(infiniteScrollEvent);
- }
- }
- };
- InfiniteScroll.prototype.shouldTriggerEvents = function (shouldScroll) {
- return (this._alwaysCallback || shouldScroll) && !this._disabled;
- };
- InfiniteScroll.prototype.ngOnDestroy = function () {
- if (this.disposeScroller) {
- this.disposeScroller.unsubscribe();
- }
- };
- InfiniteScroll.prototype.onScrollDown = function (data) {
- var _this = this;
- if (data === void 0) {
- data = { currentScrollPosition: 0 };
- }
- this.zone.run(function () {
- return _this.scrolled.emit(data);
- });
- };
- InfiniteScroll.prototype.onScrollUp = function (data) {
- var _this = this;
- if (data === void 0) {
- data = { currentScrollPosition: 0 };
- }
- this.zone.run(function () {
- return _this.scrolledUp.emit(data);
- });
- };
- InfiniteScroll.prototype.resolveContainerElement = function () {
- if (this._container) {
- return typeof this._container === 'string' ? window.document.querySelector(this._container) : this._container;
- } else {
- return this.scrollWindow ? window : this.element;
- }
- };
- InfiniteScroll.decorators = [{ type: core_1.Directive, args: [{
- selector: '[infinite-scroll]'
- }] }];
- /** @nocollapse */
- InfiniteScroll.ctorParameters = function () {
- return [{ type: core_1.ElementRef }, { type: core_1.NgZone }, { type: position_resolver_1.PositionResolverFactory }, { type: scroll_register_1.ScrollRegister }, { type: scroll_resolver_1.ScrollResolver }];
- };
- InfiniteScroll.propDecorators = {
- 'scrolled': [{ type: core_1.Output }],
- 'scrolledUp': [{ type: core_1.Output }],
- '_distanceDown': [{ type: core_1.Input, args: ['infiniteScrollDistance'] }],
- '_distanceUp': [{ type: core_1.Input, args: ['infiniteScrollUpDistance'] }],
- '_throttle': [{ type: core_1.Input, args: ['infiniteScrollThrottle'] }],
- '_disabled': [{ type: core_1.Input, args: ['infiniteScrollDisabled'] }],
- '_container': [{ type: core_1.Input, args: ['infiniteScrollContainer'] }],
- 'scrollWindow': [{ type: core_1.Input, args: ['scrollWindow'] }],
- '_immediate': [{ type: core_1.Input, args: ['immediateCheck'] }],
- '_horizontal': [{ type: core_1.Input, args: ['horizontal'] }],
- '_alwaysCallback': [{ type: core_1.Input, args: ['alwaysCallback'] }],
- 'debounce': [{ type: core_1.Input }]
- };
- return InfiniteScroll;
- }();
- exports.InfiniteScroll = InfiniteScroll;
-});
-System.registerDynamic('src/axis-resolver', ['@angular/core'], true, function ($__require, exports, module) {
- "use strict";
-
- var global = this || self,
- GLOBAL = global;
- var core_1 = $__require('@angular/core');
- var AxisResolverFactory = function () {
- function AxisResolverFactory() {}
- AxisResolverFactory.prototype.create = function (vertical) {
- if (vertical === void 0) {
- vertical = true;
- }
- return new AxisResolver(vertical);
- };
- AxisResolverFactory.decorators = [{ type: core_1.Injectable }];
- /** @nocollapse */
- AxisResolverFactory.ctorParameters = function () {
- return [];
- };
- return AxisResolverFactory;
- }();
- exports.AxisResolverFactory = AxisResolverFactory;
- var AxisResolver = function () {
- function AxisResolver(vertical) {
- if (vertical === void 0) {
- vertical = true;
- }
- this.vertical = vertical;
- }
- AxisResolver.prototype.clientHeightKey = function () {
- return this.vertical ? 'clientHeight' : 'clientWidth';
- };
- AxisResolver.prototype.offsetHeightKey = function () {
- return this.vertical ? 'offsetHeight' : 'offsetWidth';
- };
- AxisResolver.prototype.scrollHeightKey = function () {
- return this.vertical ? 'scrollHeight' : 'scrollWidth';
- };
- AxisResolver.prototype.pageYOffsetKey = function () {
- return this.vertical ? 'pageYOffset' : 'pageXOffset';
- };
- AxisResolver.prototype.offsetTopKey = function () {
- return this.vertical ? 'offsetTop' : 'offsetLeft';
- };
- AxisResolver.prototype.scrollTopKey = function () {
- return this.vertical ? 'scrollTop' : 'scrollLeft';
- };
- AxisResolver.prototype.topKey = function () {
- return this.vertical ? 'top' : 'left';
- };
- return AxisResolver;
- }();
- exports.AxisResolver = AxisResolver;
-});
-System.registerDynamic('src/position-resolver', ['@angular/core', './axis-resolver'], true, function ($__require, exports, module) {
- "use strict";
-
- var global = this || self,
- GLOBAL = global;
- var core_1 = $__require('@angular/core');
- var axis_resolver_1 = $__require('./axis-resolver');
- var PositionResolverFactory = function () {
- function PositionResolverFactory(axisResolver) {
- this.axisResolver = axisResolver;
- }
- PositionResolverFactory.prototype.create = function (options) {
- return new PositionResolver(this.axisResolver.create(!options.horizontal), options);
- };
- PositionResolverFactory.decorators = [{ type: core_1.Injectable }];
- /** @nocollapse */
- PositionResolverFactory.ctorParameters = function () {
- return [{ type: axis_resolver_1.AxisResolverFactory }];
- };
- return PositionResolverFactory;
- }();
- exports.PositionResolverFactory = PositionResolverFactory;
- var PositionResolver = function () {
- function PositionResolver(axis, options) {
- this.axis = axis;
- this.options = options;
- this.resolveContainer(this.options.windowElement);
- this.defineContainer(this.options.windowElement);
- }
- PositionResolver.prototype.defineContainer = function (windowElement) {
- if (this.resolveContainer(windowElement) || !windowElement.nativeElement) {
- this.container = windowElement;
- } else {
- this.container = windowElement.nativeElement;
- }
- return this.container;
- };
- PositionResolver.prototype.resolveContainer = function (windowElement) {
- var isContainerWindow = Object.prototype.toString.call(windowElement).includes('Window');
- this.isContainerWindow = isContainerWindow;
- return isContainerWindow;
- };
- PositionResolver.prototype.getDocumentElement = function () {
- return this.isContainerWindow ? this.options.windowElement.document.documentElement : null;
- };
- PositionResolver.prototype.calculatePoints = function (element) {
- return this.isContainerWindow ? this.calculatePointsForWindow(element) : this.calculatePointsForElement(element);
- };
- PositionResolver.prototype.calculatePointsForWindow = function (element) {
- // container's height
- var height = this.height(this.container);
- // scrolled until now / current y point
- var scrolledUntilNow = height + this.pageYOffset(this.getDocumentElement());
- // total height / most bottom y point
- var totalToScroll = this.offsetTop(element.nativeElement) + this.height(element.nativeElement);
- return { height: height, scrolledUntilNow: scrolledUntilNow, totalToScroll: totalToScroll };
- };
- PositionResolver.prototype.calculatePointsForElement = function (element) {
- var scrollTop = this.axis.scrollTopKey();
- var scrollHeight = this.axis.scrollHeightKey();
- var container = this.container;
- var height = this.height(container);
- // perhaps use this.container.offsetTop instead of 'scrollTop'
- var scrolledUntilNow = container[scrollTop];
- var containerTopOffset = 0;
- var offsetTop = this.offsetTop(container);
- if (offsetTop !== void 0) {
- containerTopOffset = offsetTop;
- }
- var totalToScroll = container[scrollHeight];
- return { height: height, scrolledUntilNow: scrolledUntilNow, totalToScroll: totalToScroll };
- };
- PositionResolver.prototype.height = function (elem) {
- var offsetHeight = this.axis.offsetHeightKey();
- var clientHeight = this.axis.clientHeightKey();
- // elem = elem.nativeElement;
- if (isNaN(elem[offsetHeight])) {
- return this.getDocumentElement()[clientHeight];
- } else {
- return elem[offsetHeight];
- }
- };
- PositionResolver.prototype.offsetTop = function (elem) {
- var top = this.axis.topKey();
- // elem = elem.nativeElement;
- if (!elem.getBoundingClientRect) {
- return;
- }
- return elem.getBoundingClientRect()[top] + this.pageYOffset(elem);
- };
- PositionResolver.prototype.pageYOffset = function (elem) {
- var pageYOffset = this.axis.pageYOffsetKey();
- var scrollTop = this.axis.scrollTopKey();
- var offsetTop = this.axis.offsetTopKey();
- // elem = elem.nativeElement;
- if (isNaN(window[pageYOffset])) {
- return this.getDocumentElement()[scrollTop];
- } else if (elem.ownerDocument) {
- return elem.ownerDocument.defaultView[pageYOffset];
- } else {
- return elem[offsetTop];
- }
- };
- return PositionResolver;
- }();
- exports.PositionResolver = PositionResolver;
-});
-System.registerDynamic('src/index', ['@angular/core', './infinite-scroll', './axis-resolver', './position-resolver', './scroll-register', './scroll-resolver'], true, function ($__require, exports, module) {
- "use strict";
-
- var global = this || self,
- GLOBAL = global;
- var core_1 = $__require('@angular/core');
- var infinite_scroll_1 = $__require('./infinite-scroll');
- var axis_resolver_1 = $__require('./axis-resolver');
- var position_resolver_1 = $__require('./position-resolver');
- var scroll_register_1 = $__require('./scroll-register');
- var scroll_resolver_1 = $__require('./scroll-resolver');
- var InfiniteScrollModule = function () {
- function InfiniteScrollModule() {}
- InfiniteScrollModule.decorators = [{ type: core_1.NgModule, args: [{
- imports: [],
- declarations: [infinite_scroll_1.InfiniteScroll],
- exports: [infinite_scroll_1.InfiniteScroll],
- providers: [axis_resolver_1.AxisResolverFactory, position_resolver_1.PositionResolverFactory, scroll_register_1.ScrollRegister, scroll_resolver_1.ScrollResolver]
- }] }];
- /** @nocollapse */
- InfiniteScrollModule.ctorParameters = function () {
- return [];
- };
- return InfiniteScrollModule;
- }();
- exports.InfiniteScrollModule = InfiniteScrollModule;
-});
-System.registerDynamic('src/scroll-register', ['@angular/core', 'rxjs/Observable', 'rxjs/add/observable/fromEvent', 'rxjs/add/observable/timer', 'rxjs/add/observable/of', 'rxjs/add/operator/debounce', 'rxjs/add/operator/throttle', 'rxjs/add/operator/filter', 'rxjs/add/operator/mergeMap'], true, function ($__require, exports, module) {
- "use strict";
-
- var global = this || self,
- GLOBAL = global;
- var core_1 = $__require('@angular/core');
- var Observable_1 = $__require('rxjs/Observable');
- $__require('rxjs/add/observable/fromEvent');
- $__require('rxjs/add/observable/timer');
- $__require('rxjs/add/observable/of');
- $__require('rxjs/add/operator/debounce');
- $__require('rxjs/add/operator/throttle');
- $__require('rxjs/add/operator/filter');
- $__require('rxjs/add/operator/mergeMap');
- var ScrollRegister = function () {
- function ScrollRegister() {}
- ScrollRegister.prototype.attachEvent = function (options) {
- var scroller$ = Observable_1.Observable.fromEvent(options.container, 'scroll')[options.throttleType](function () {
- return Observable_1.Observable.timer(options.throttleDuration);
- }).filter(options.filterBefore).mergeMap(function (ev) {
- return Observable_1.Observable.of(options.mergeMap(ev));
- }).subscribe(options.scrollHandler);
- return scroller$;
- };
- ScrollRegister.decorators = [{ type: core_1.Injectable }];
- /** @nocollapse */
- ScrollRegister.ctorParameters = function () {
- return [];
- };
- return ScrollRegister;
- }();
- exports.ScrollRegister = ScrollRegister;
-});
-System.registerDynamic("src/scroll-resolver", ["@angular/core"], true, function ($__require, exports, module) {
- "use strict";
-
- var global = this || self,
- GLOBAL = global;
- var core_1 = $__require('@angular/core');
- var ScrollResolver = function () {
- function ScrollResolver() {
- this.lastScrollPosition = 0;
- }
- ScrollResolver.prototype.shouldScroll = function (container, config, scrollingDown) {
- var distance = config.distance;
- var remaining;
- var containerBreakpoint;
- if (scrollingDown) {
- remaining = container.totalToScroll - container.scrolledUntilNow;
- containerBreakpoint = container.height * distance.down + 1;
- } else {
- remaining = container.scrolledUntilNow;
- containerBreakpoint = container.height * distance.up + 1;
- }
- var shouldScroll = remaining <= containerBreakpoint;
- this.lastScrollPosition = container.scrolledUntilNow;
- return shouldScroll;
- };
- ScrollResolver.prototype.isScrollingDown = function (container) {
- return this.lastScrollPosition < container.scrolledUntilNow;
- };
- ScrollResolver.prototype.getScrollStats = function (container, config) {
- var isScrollingDown = this.isScrollingDown(container);
- var shouldScroll = this.shouldScroll(container, config, isScrollingDown);
- return { isScrollingDown: isScrollingDown, shouldScroll: shouldScroll };
- };
- ScrollResolver.decorators = [{ type: core_1.Injectable }];
- /** @nocollapse */
- ScrollResolver.ctorParameters = function () {
- return [];
- };
- return ScrollResolver;
- }();
- exports.ScrollResolver = ScrollResolver;
-});
-System.registerDynamic('angular2-infinite-scroll', ['./src/infinite-scroll', './src/position-resolver', './src/axis-resolver', './src/index', './src/scroll-register', './src/scroll-resolver'], true, function ($__require, exports, module) {
- "use strict";
-
- var global = this || self,
- GLOBAL = global;
- var infinite_scroll_1 = $__require('./src/infinite-scroll');
- var position_resolver_1 = $__require('./src/position-resolver');
- var axis_resolver_1 = $__require('./src/axis-resolver');
- var infinite_scroll_2 = $__require('./src/infinite-scroll');
- exports.InfiniteScroll = infinite_scroll_2.InfiniteScroll;
- var position_resolver_2 = $__require('./src/position-resolver');
- exports.PositionResolver = position_resolver_2.PositionResolver;
- exports.PositionResolverFactory = position_resolver_2.PositionResolverFactory;
- var axis_resolver_2 = $__require('./src/axis-resolver');
- exports.AxisResolver = axis_resolver_2.AxisResolver;
- exports.AxisResolverFactory = axis_resolver_2.AxisResolverFactory;
- var index_1 = $__require('./src/index');
- exports.InfiniteScrollModule = index_1.InfiniteScrollModule;
- var scroll_register_1 = $__require('./src/scroll-register');
- exports.ScrollRegister = scroll_register_1.ScrollRegister;
- var scroll_resolver_1 = $__require('./src/scroll-resolver');
- exports.ScrollResolver = scroll_resolver_1.ScrollResolver;
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.default = {
- directives: [infinite_scroll_1.InfiniteScroll, axis_resolver_1.AxisResolver, position_resolver_1.PositionResolver]
- };
-});
\ No newline at end of file
diff --git a/index.ts b/index.ts
new file mode 100644
index 00000000..4aaf8f92
--- /dev/null
+++ b/index.ts
@@ -0,0 +1 @@
+export * from './public_api';
diff --git a/karma.conf.js b/karma.conf.js
index 75eda637..42dec8fc 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -1,2 +1,96 @@
-// Look in ./config for karma.conf.js
-module.exports = require('./config/karma.conf.js');
+// Karma configuration for Unit testing
+
+module.exports = function (config) {
+
+ var configuration = {
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-webpack'),
+ require('karma-sourcemap-loader'),
+ require('karma-spec-reporter')
+ ],
+
+ // list of files / patterns to load in the browser
+ files: [
+ { pattern: 'spec.bundle.js', watched: false }
+ ],
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'spec.bundle.js': ['webpack']
+ },
+
+ // webpack
+ webpack: {
+ resolve: {
+ extensions: ['.ts', '.js']
+ },
+ module: {
+ rules: [
+ {
+ test: /\.ts/,
+ loaders: ['ts-loader'],
+ exclude: /node_modules/
+ }
+ ],
+ exprContextCritical: false
+ },
+ performance: { hints: false }
+ },
+
+ webpackServer: {
+ noInfo: true
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['spec'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: true
+
+ };
+
+ config.set(configuration);
+
+}
diff --git a/.eslintrc.json b/old/.eslintrc.json
similarity index 100%
rename from .eslintrc.json
rename to old/.eslintrc.json
diff --git a/old/.travis.yml b/old/.travis.yml
new file mode 100644
index 00000000..ac1c2de6
--- /dev/null
+++ b/old/.travis.yml
@@ -0,0 +1,14 @@
+language: node_js
+sudo: false
+node_js:
+- '5.9.1'
+
+install:
+- npm install
+
+after_script:
+- process.exit()
+
+cache:
+ directories:
+ - $HOME/.nvm
\ No newline at end of file
diff --git a/old/CHANGELOG.md b/old/CHANGELOG.md
new file mode 100644
index 00000000..49e8a5b2
--- /dev/null
+++ b/old/CHANGELOG.md
@@ -0,0 +1,27 @@
+## v 0.3.3 (2017/03/01)
+
+### Updates
+* reverted the fix of #126
+
+## v 0.3.2 (2017/03/01)
+
+### Updates
+* fixes [#126](https://github.com/orizens/angular2-infinite-scroll/issues/126)
+
+## v 0.3.1 (2017/02/15)
+
+### Updates
+* added custom scrollable container from [#108](https://github.com/orizens/angular2-infinite-scroll/pull/108/files)
+
+## v 0.3.0 (2017/01/31)
+
+### Updates
+* refactored infinite scroller to smaller modules with composition
+* added "models" - includes interfaces for development
+
+## v 0.2.9 (2017/01/13)
+
+### Updates
+* added changelog.md
+* ([refactor(scroll): replaces throttle with debounce](https://github.com/orizens/angular2-infinite-scroll/pull/82))
+
diff --git a/old/LICENSE b/old/LICENSE
new file mode 100644
index 00000000..5ef2594b
--- /dev/null
+++ b/old/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Oren Farhi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/old/README.md b/old/README.md
new file mode 100644
index 00000000..ecce7fe6
--- /dev/null
+++ b/old/README.md
@@ -0,0 +1,148 @@
+[![Build Status](https://travis-ci.org/orizens/angular2-infinite-scroll.svg?branch=master)](https://travis-ci.org/orizens/angular2-infinite-scroll)
+
+# Angular Infinite Scroll
+Inspired by [ng-infinite-scroll](https://github.com/sroze/ngInfiniteScroll) directive for angular (> 2).
+
+## Angular Support
+Supports in Angular **> +2, 4**
+
+## Angular Consulting Services
+I'm a Senior Javascript Engineer & A Front End Consultant at [Orizens](http://orizens.com).
+My services include:
+- consulting to companies and startups on how to approach code in their projects and keep it maintainable.
+- I provide project bootstrapping and development - while afterwards, I integrate it on site and guide the team on it.
+
+[Contact Me Here](http://orizens.com/contact)
+
+## Installation
+```
+npm install angular2-infinite-scroll --save
+```
+
+## Supported API
+Currently supported attributes:
+* **infiniteScrollDistance**<_number_> - (optional, default: **2**) - should get a number, the number of viewport lenghts from the bottom of the page at which the event will be triggered.
+* **infiniteScrollUpDistance**<_number_> - (optional, default: **1.5**) - should get a number
+* **infiniteScrollThrottle**<_number_> - (optional, default: **300**) - should get a number of **milliseconds** for throttle. The event will be triggered this many milliseconds after the user *stops* scrolling.
+* **infiniteScrollContainer**<_string|HTMLElement_> - (optional, default: null) - should get a html element or css selector for a scrollable element; window or current element will be used if this attribute is empty.
+* **scrolled**<_function_> - this will callback if the distance threshold has been reached on a scroll down.
+* **scrolledUp**<_function_> - (event: InfiniteScrollEvent) - this will callback if the distance threshold has been reached on a scroll up.
+* **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
+
+## Behavior
+By default, the directive listens to the **window scroll** event and invoked the callback.
+**To trigger the callback when the actual element is scrolled**, these settings should be configured:
+* [scrollWindow]="false"
+* set an explict css "height" value to the element
+
+## DEMO
+- [**Default Scroll** By Window - plunkr](https://plnkr.co/edit/DrEDetYnZkFxR7OWWrxS?p=preview)
+- [Scroll On a **"Modal"** - plunkr](https://plnkr.co/edit/QnQOwE9SEapwJCCFII3L?p=preview)
+
+## Usage
+First, import the InfiniteScrollModule to your module:
+
+```typescript
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { InfiniteScrollModule } from 'angular2-infinite-scroll';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { AppComponent } from './app';
+
+@NgModule({
+ imports:[ BrowserModule, InfiniteScrollModule ],
+ declarations: [ AppComponent, ],
+ bootstrap: [ AppComponent ]
+})
+export class AppModule { }
+
+platformBrowserDynamic().bootstrapModule(AppModule);
+```
+
+In this example, the **onScroll** callback will be invoked when the window is scrolled down:
+
+```typescript
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app',
+ template: `
+
+
+ `
+})
+export class AppComponent {
+ onScroll () {
+ console.log('scrolled!!')
+ }
+}
+```
+in this example, whenever the "search-results" is scrolled, the callback will be invoked:
+
+```typescript
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app',
+ styles: [
+ `.search-results {
+ height: 20rem;
+ overflow: scroll;
+ }`
+ ],
+ template: `
+
+
+ `
+})
+export class AppComponent {
+ onScroll () {
+ console.log('scrolled!!')
+ }
+}
+```
+
+In this example, the **onScrollDown** callback will be invoked when the window is scrolled down and the **onScrollUp** callback will be invoked when the window is scrolled up:
+
+```typescript
+import { Component } from '@angular/core';
+import { InfiniteScroll } from 'angular2-infinite-scroll';
+
+@Component({
+ selector: 'app',
+ directives: [ InfiniteScroll ],
+ template: `
+
+
+ `
+})
+export class AppComponent {
+ onScrollDown () {
+ console.log('scrolled down!!')
+ }
+
+ onScrollUp () {
+ console.log('scrolled up!!')
+ }
+}
+```
+
+# Showcase Examples
+* [Echoes Player Ng2 Version](http://orizens.github.io/echoes-ng2) ([github repo for echoes player](http://github.com/orizens/echoes-ng2))
diff --git a/angular2-infinite-scroll.ts b/old/angular2-infinite-scroll.ts
similarity index 100%
rename from angular2-infinite-scroll.ts
rename to old/angular2-infinite-scroll.ts
diff --git a/config/helpers.js b/old/config/helpers.js
similarity index 100%
rename from config/helpers.js
rename to old/config/helpers.js
diff --git a/config/karma.conf.js b/old/config/karma.conf.js
similarity index 100%
rename from config/karma.conf.js
rename to old/config/karma.conf.js
diff --git a/config/spec-bundle.js b/old/config/spec-bundle.js
similarity index 100%
rename from config/spec-bundle.js
rename to old/config/spec-bundle.js
diff --git a/config/testing-utils.js b/old/config/testing-utils.js
similarity index 100%
rename from config/testing-utils.js
rename to old/config/testing-utils.js
diff --git a/config/webpack.test.js b/old/config/webpack.test.js
similarity index 100%
rename from config/webpack.test.js
rename to old/config/webpack.test.js
diff --git a/karma-test-shim.js b/old/karma-test-shim.js
similarity index 100%
rename from karma-test-shim.js
rename to old/karma-test-shim.js
diff --git a/old/karma.conf.js b/old/karma.conf.js
new file mode 100644
index 00000000..75eda637
--- /dev/null
+++ b/old/karma.conf.js
@@ -0,0 +1,2 @@
+// Look in ./config for karma.conf.js
+module.exports = require('./config/karma.conf.js');
diff --git a/make.js b/old/make.js
similarity index 100%
rename from make.js
rename to old/make.js
diff --git a/old/package.json b/old/package.json
new file mode 100644
index 00000000..5bcce568
--- /dev/null
+++ b/old/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "angular2-infinite-scroll",
+ "version": "0.3.4",
+ "description": "An infinite scroll directive for angular2",
+ "main": "angular2-infinite-scroll.js",
+ "typings": "./angular2-infinite-scroll.d.ts",
+ "repository": "orizens/angular2-infinite-scroll",
+ "scripts": {
+ "build:test": "tsc --project ./src",
+ "clean": "npm run clean:jsfiles && npm run clean:typefiles && npm run clean:metadata",
+ "clean:jsfiles": "rimraf src/*.js && rimraf ./*scroll.js",
+ "clean:typefiles": "rimraf src/*.d.ts && rimraf ./*scroll.d.ts",
+ "clean:node": "rimraf node_modules",
+ "clean:metadata": "rimraf ./*metadata.json && rimraf src/ngfactory && rimraf ./src/*.metadata.json",
+ "bdd": "NODE_ENV='bdd' karma start karma.conf.js",
+ "lite": "lite-server",
+ "postversion": "git push origin master",
+ "prepublish": "node ./node_modules/@angular/compiler-cli/src/main.js && node make.js",
+ "pretest": "npm run clean",
+ "preversion": "npm run clean && npm run prepublish && npm test",
+ "setup": "npm run typings -- install",
+ "start": "npm run build && npm run lite",
+ "test": "karma start karma.conf.js",
+ "version": "git add ./",
+ "watch": "tsc --project ./src --watch"
+ },
+ "keywords": [
+ "angular2",
+ "scroll",
+ "infinite"
+ ],
+ "author": "Oren Farhi",
+ "license": "MIT",
+ "devDependencies": {
+ "@angular/common": "2.3.1",
+ "@angular/compiler": "2.3.1",
+ "@angular/compiler-cli": "2.3.1",
+ "@angular/core": "2.3.1",
+ "@angular/platform-browser": "2.3.1",
+ "@angular/platform-browser-dynamic": "2.3.1",
+ "@types/core-js": "0.9.32",
+ "@types/jasmine": "2.2.33",
+ "@types/node": "6.0.38",
+ "autodts": "0.0.6",
+ "awesome-typescript-loader": "2.2.4",
+ "es6-promise": "3.0.2",
+ "es6-shim": "0.33.3",
+ "istanbul-instrumenter-loader": "^1.0.0",
+ "jasmine-core": "2.4.1",
+ "karma": "^0.13.22",
+ "karma-chrome-launcher": "^1.0.1",
+ "karma-cli": "^1.0.0",
+ "karma-jasmine": "^1.0.2",
+ "karma-mocha-reporter": "2.0.4",
+ "karma-phantomjs-launcher": "1.0.0",
+ "karma-sourcemap-loader": "^0.3.7",
+ "karma-webpack": "^1.8.0",
+ "lite-server": "2.2.0",
+ "path": "^0.12.7",
+ "phantomjs-prebuilt": "^2.1.7",
+ "reflect-metadata": "0.1.2",
+ "rimraf": "2.5.2",
+ "rxjs": "5.0.2",
+ "source-map-loader": "^0.1.5",
+ "systemjs": "0.19.31",
+ "systemjs-builder": "^0.15.16",
+ "ts-helpers": "^1.1.1",
+ "tslint": "^3.15.1",
+ "tslint-loader": "^2.1.5",
+ "typescript": "2.0.3",
+ "webpack": "2.1.0-beta.21",
+ "zone.js": "^0.6.17"
+ }
+}
diff --git a/src/axis-resolver.spec.ts b/old/src/axis-resolver.spec.ts
similarity index 100%
rename from src/axis-resolver.spec.ts
rename to old/src/axis-resolver.spec.ts
diff --git a/src/axis-resolver.ts b/old/src/axis-resolver.ts
similarity index 100%
rename from src/axis-resolver.ts
rename to old/src/axis-resolver.ts
diff --git a/src/index.ts b/old/src/index.ts
similarity index 100%
rename from src/index.ts
rename to old/src/index.ts
diff --git a/src/infinite-scroll.spec.ts b/old/src/infinite-scroll.spec.ts
similarity index 100%
rename from src/infinite-scroll.spec.ts
rename to old/src/infinite-scroll.spec.ts
diff --git a/src/infinite-scroll.ts b/old/src/infinite-scroll.ts
similarity index 100%
rename from src/infinite-scroll.ts
rename to old/src/infinite-scroll.ts
diff --git a/old/src/models.ts b/old/src/models.ts
new file mode 100644
index 00000000..c6ee43df
--- /dev/null
+++ b/old/src/models.ts
@@ -0,0 +1,31 @@
+import { ElementRef } from '@angular/core';
+
+export type ContainerRef = Window | ElementRef | any;
+
+export interface InfiniteScrollEvent {
+ currentScrollPosition: number;
+};
+
+export interface PositionElements {
+ windowElement: ContainerRef;
+ horizontal: boolean;
+}
+
+export interface PositionStats {
+ height: number;
+ scrolledUntilNow: number;
+ totalToScroll: number;
+}
+
+export interface ScrollerConfig {
+ distance: {
+ down: number;
+ up: number;
+ };
+ scrollParent?: ContainerRef;
+}
+
+export interface ScrollStats {
+ isScrollingDown: boolean;
+ shouldScroll: boolean
+}
diff --git a/src/position-resolver.spec.ts b/old/src/position-resolver.spec.ts
similarity index 100%
rename from src/position-resolver.spec.ts
rename to old/src/position-resolver.spec.ts
diff --git a/src/position-resolver.ts b/old/src/position-resolver.ts
similarity index 100%
rename from src/position-resolver.ts
rename to old/src/position-resolver.ts
diff --git a/src/scroll-register.spec.ts b/old/src/scroll-register.spec.ts
similarity index 100%
rename from src/scroll-register.spec.ts
rename to old/src/scroll-register.spec.ts
diff --git a/src/scroll-register.ts b/old/src/scroll-register.ts
similarity index 100%
rename from src/scroll-register.ts
rename to old/src/scroll-register.ts
diff --git a/src/scroll-resolver.ts b/old/src/scroll-resolver.ts
similarity index 100%
rename from src/scroll-resolver.ts
rename to old/src/scroll-resolver.ts
diff --git a/src/tsconfig.json b/old/src/tsconfig.json
similarity index 100%
rename from src/tsconfig.json
rename to old/src/tsconfig.json
diff --git a/old/tsconfig.json b/old/tsconfig.json
new file mode 100644
index 00000000..65877efd
--- /dev/null
+++ b/old/tsconfig.json
@@ -0,0 +1,47 @@
+{
+ "compilerOptions": {
+ "noImplicitAny": true,
+ "module": "commonjs",
+ "target": "es5",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "inlineSourceMap": true,
+ "inlineSources": true,
+ "declaration": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "moduleResolution": "node",
+ "lib": [
+ "dom",
+ "es6"
+ ],
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "exclude": [
+ "node_modules",
+ "bundles"
+ ],
+ "awesomeTypescriptLoaderOptions": {
+ "forkChecker": true,
+ "useWebpackText": true
+ },
+ "files": [
+ "./angular2-infinite-scroll.ts",
+ "./src/infinite-scroll.ts",
+ "./src/infinite-scroll.spec.ts",
+ "./src/axis-resolver.ts",
+ "./src/axis-resolver.spec.ts",
+ "./src/position-resolver.ts",
+ "./src/position-resolver.spec.ts",
+ "./src/scroll-resolver.ts",
+ "./src/scroll-register.ts",
+ "./src/models.ts",
+ "./src/index.ts"
+ ],
+ "angularCompilerOptions": {
+ "genDir": "./src/ngfactory",
+ "debug": false
+ }
+}
diff --git a/package.json b/package.json
index 5bcce568..af4dee4d 100644
--- a/package.json
+++ b/package.json
@@ -1,74 +1,72 @@
{
- "name": "angular2-infinite-scroll",
- "version": "0.3.4",
- "description": "An infinite scroll directive for angular2",
- "main": "angular2-infinite-scroll.js",
- "typings": "./angular2-infinite-scroll.d.ts",
- "repository": "orizens/angular2-infinite-scroll",
- "scripts": {
- "build:test": "tsc --project ./src",
- "clean": "npm run clean:jsfiles && npm run clean:typefiles && npm run clean:metadata",
- "clean:jsfiles": "rimraf src/*.js && rimraf ./*scroll.js",
- "clean:typefiles": "rimraf src/*.d.ts && rimraf ./*scroll.d.ts",
- "clean:node": "rimraf node_modules",
- "clean:metadata": "rimraf ./*metadata.json && rimraf src/ngfactory && rimraf ./src/*.metadata.json",
- "bdd": "NODE_ENV='bdd' karma start karma.conf.js",
- "lite": "lite-server",
- "postversion": "git push origin master",
- "prepublish": "node ./node_modules/@angular/compiler-cli/src/main.js && node make.js",
- "pretest": "npm run clean",
- "preversion": "npm run clean && npm run prepublish && npm test",
- "setup": "npm run typings -- install",
- "start": "npm run build && npm run lite",
- "test": "karma start karma.conf.js",
- "version": "git add ./",
- "watch": "tsc --project ./src --watch"
- },
- "keywords": [
- "angular2",
- "scroll",
- "infinite"
- ],
- "author": "Oren Farhi",
- "license": "MIT",
- "devDependencies": {
- "@angular/common": "2.3.1",
- "@angular/compiler": "2.3.1",
- "@angular/compiler-cli": "2.3.1",
- "@angular/core": "2.3.1",
- "@angular/platform-browser": "2.3.1",
- "@angular/platform-browser-dynamic": "2.3.1",
- "@types/core-js": "0.9.32",
- "@types/jasmine": "2.2.33",
- "@types/node": "6.0.38",
- "autodts": "0.0.6",
- "awesome-typescript-loader": "2.2.4",
- "es6-promise": "3.0.2",
- "es6-shim": "0.33.3",
- "istanbul-instrumenter-loader": "^1.0.0",
- "jasmine-core": "2.4.1",
- "karma": "^0.13.22",
- "karma-chrome-launcher": "^1.0.1",
- "karma-cli": "^1.0.0",
- "karma-jasmine": "^1.0.2",
- "karma-mocha-reporter": "2.0.4",
- "karma-phantomjs-launcher": "1.0.0",
- "karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^1.8.0",
- "lite-server": "2.2.0",
- "path": "^0.12.7",
- "phantomjs-prebuilt": "^2.1.7",
- "reflect-metadata": "0.1.2",
- "rimraf": "2.5.2",
- "rxjs": "5.0.2",
- "source-map-loader": "^0.1.5",
- "systemjs": "0.19.31",
- "systemjs-builder": "^0.15.16",
- "ts-helpers": "^1.1.1",
- "tslint": "^3.15.1",
- "tslint-loader": "^2.1.5",
- "typescript": "2.0.3",
- "webpack": "2.1.0-beta.21",
- "zone.js": "^0.6.17"
- }
-}
+ "name": "ngx-infinite-scroll",
+ "version": "0.4.0-0",
+ "description": "Build an Angular library compatible with AoT compilation and Tree shaking",
+ "main": "./bundles/ngx-infinite-scroll.umd.js",
+ "module": "./modules/ngx-infinite-scroll.es5.js",
+ "es2015": "./modules/ngx-infinite-scroll.js",
+ "scripts": {
+ "build": "node build.js",
+ "test": "karma start",
+ "pack-lib": "npm pack ./dist",
+ "publish-lib": "npm publish ./dist",
+ "compodoc": "compodoc -p tsconfig.json",
+ "compodoc-serve": "compodoc -s"
+ },
+ "typings": "./index.d.ts",
+ "author": "",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/orizens/ngx-infinite-scroll.git"
+ },
+ "bugs": {
+ "url": "https://github.com/orizens/ngx-infinite-scroll/issues"
+ },
+ "homepage": "https://github.com/orizens/ngx-infinite-scroll",
+ "keywords": [
+ "angular",
+ "javascript",
+ "typescript"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@angular/common": ">= 4.0.0",
+ "@angular/core": ">= 4.0.0"
+ },
+ "devDependencies": {
+ "@angular/common": "4.0.0",
+ "@angular/compiler": "4.0.0",
+ "@angular/compiler-cli": "4.0.0",
+ "@angular/core": "4.0.0",
+ "@angular/platform-browser": "4.0.0",
+ "@angular/platform-browser-dynamic": "4.0.0",
+ "@angular/platform-server": "4.0.0",
+ "@angular/animations": "4.0.0",
+ "@types/jasmine": "2.5.46",
+ "@types/node": "7.0.10",
+ "codelyzer": "3.0.0-beta.4",
+ "compodoc": "0.0.41",
+ "core-js": "2.4.1",
+ "jasmine-core": "2.5.2",
+ "karma": "1.5.0",
+ "karma-chrome-launcher": "2.0.0",
+ "karma-jasmine": "1.1.0",
+ "karma-sourcemap-loader": "0.3.7",
+ "karma-spec-reporter": "0.0.30",
+ "karma-webpack": "2.0.3",
+ "reflect-metadata": "0.1.10",
+ "rollup": "0.41.6",
+ "rxjs": "5.2.0",
+ "ts-helpers": "1.1.2",
+ "ts-loader": "2.0.3",
+ "tslint": "4.5.1",
+ "typescript": "^2.1.5",
+ "webpack": "2.3.1",
+ "zone.js": "0.8.5",
+ "shelljs": "0.7.7",
+ "chalk": "1.1.3",
+ "uglify-js": "2.8.15",
+ "sorcery": "0.10.0",
+ "yargs": "7.0.2"
+ }
+}
\ No newline at end of file
diff --git a/public_api.ts b/public_api.ts
new file mode 100644
index 00000000..cac48cee
--- /dev/null
+++ b/public_api.ts
@@ -0,0 +1,12 @@
+/**
+ * Angular library starter.
+ * Build an Angular library compatible with AoT compilation & Tree shaking.
+ * Written by Roberto Simonetti.
+ * MIT license.
+ * https://github.com/robisim74/angular-library-starter
+ */
+
+/**
+ * Entry point for all public APIs of the package.
+ */
+export * from './src/ngx-infinite-scroll';
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 00000000..da2fe839
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,20 @@
+export default {
+ entry: './dist/modules/angular-library-starter.es5.js',
+ dest: './dist/bundles/angular-library-starter.umd.js',
+ format: 'umd',
+ exports: 'named',
+ moduleName: 'ng.angularLibraryStarter',
+ external: [
+ '@angular/core',
+ '@angular/common',
+ 'rxjs/Observable',
+ 'rxjs/Observer'
+ ],
+ globals: {
+ '@angular/core': 'ng.core',
+ '@angular/common': 'ng.common',
+ 'rxjs/Observable': 'Rx',
+ 'rxjs/Observer': 'Rx'
+ },
+ onwarn: () => { return }
+}
\ No newline at end of file
diff --git a/scripts/map-sources.js b/scripts/map-sources.js
new file mode 100644
index 00000000..1d53166b
--- /dev/null
+++ b/scripts/map-sources.js
@@ -0,0 +1,9 @@
+const sorcery = require('sorcery');
+
+var argv = require('yargs')
+ .alias('f', 'file')
+ .argv;
+
+sorcery.load(argv.file).then(function(chain) {
+ chain.write();
+});
diff --git a/spec.bundle.js b/spec.bundle.js
new file mode 100644
index 00000000..b1890cfb
--- /dev/null
+++ b/spec.bundle.js
@@ -0,0 +1,28 @@
+Error.stackTraceLimit = Infinity;
+
+require('core-js');
+require('ts-helpers');
+require('zone.js/dist/zone');
+require('zone.js/dist/long-stack-trace-zone');
+require('zone.js/dist/proxy');
+require('zone.js/dist/sync-test');
+require('zone.js/dist/jasmine-patch');
+require('zone.js/dist/async-test');
+require('zone.js/dist/fake-async-test');
+require('rxjs/Rx');
+
+var testing = require('@angular/core/testing');
+var browser = require('@angular/platform-browser-dynamic/testing');
+
+testing.TestBed.initTestEnvironment(
+ browser.BrowserDynamicTestingModule,
+ browser.platformBrowserDynamicTesting()
+);
+
+var testContext = require.context('./tests', true, /\.spec\.ts/);
+
+function requireAll(requireContext) {
+ return requireContext.keys().map(requireContext);
+}
+
+var modules = requireAll(testContext);
diff --git a/src/modules/arithmetic-module.ts b/src/modules/arithmetic-module.ts
new file mode 100644
index 00000000..db0d4837
--- /dev/null
+++ b/src/modules/arithmetic-module.ts
@@ -0,0 +1,37 @@
+import { NgModule, ModuleWithProviders } from '@angular/core';
+
+import { SumService } from '../services/sum-service';
+
+@NgModule({
+ declarations: [
+ // Pipes.
+ // Directives.
+ ],
+ exports: [
+ // Pipes.
+ // Directives.
+ ]
+})
+export class ArithmeticModule {
+
+ /**
+ * Use in AppModule: new instance of SumService.
+ */
+ public static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: ArithmeticModule,
+ providers: [SumService]
+ };
+ }
+
+ /**
+ * Use in features modules with lazy loading: new instance of SumService.
+ */
+ public static forChild(): ModuleWithProviders {
+ return {
+ ngModule: ArithmeticModule,
+ providers: [SumService]
+ };
+ }
+
+}
diff --git a/src/modules/index.ts b/src/modules/index.ts
new file mode 100644
index 00000000..a54cccc8
--- /dev/null
+++ b/src/modules/index.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+
+import { InfiniteScroll } from './infinite-scroll';
+import { AxisResolverFactory } from '../services/axis-resolver';
+import { PositionResolverFactory } from '../services/position-resolver';
+import { ScrollRegister } from '../services/scroll-register';
+import { ScrollResolver } from '../services/scroll-resolver';
+
+@NgModule({
+ imports: [],
+ declarations: [InfiniteScroll],
+ exports: [InfiniteScroll],
+ providers: [
+ AxisResolverFactory,
+ PositionResolverFactory,
+ ScrollRegister,
+ ScrollResolver
+ ]
+})
+export class InfiniteScrollModule { }
diff --git a/src/modules/infinite-scroll.ts b/src/modules/infinite-scroll.ts
new file mode 100644
index 00000000..4a2b1257
--- /dev/null
+++ b/src/modules/infinite-scroll.ts
@@ -0,0 +1,109 @@
+import { InfiniteScrollEvent, ScrollStats, PositionStats } from '../models';
+import {
+ Directive, ElementRef, Input, Output,
+ EventEmitter, OnDestroy, OnInit,
+ SimpleChanges, NgZone
+} from '@angular/core';
+import { PositionResolverFactory } from '../services/position-resolver';
+import { ScrollRegister, ScrollRegisterConfig } from '../services/scroll-register';
+import { ScrollResolver } from '../services/scroll-resolver';
+import { Subscription } from 'rxjs/Rx';
+
+
+@Directive({
+ selector: '[infinite-scroll]'
+})
+export class InfiniteScroll implements OnDestroy, OnInit {
+ @Output() scrolled = new EventEmitter();
+ @Output() scrolledUp = new EventEmitter();
+
+ @Input('infiniteScrollDistance') _distanceDown: number = 2;
+ @Input('infiniteScrollUpDistance') _distanceUp: number = 1.5;
+ @Input('infiniteScrollThrottle') _throttle: number = 300;
+ @Input('infiniteScrollDisabled') _disabled: boolean = false;
+ @Input('infiniteScrollContainer') _container: any = null;
+ @Input('scrollWindow') scrollWindow: boolean = true;
+ @Input('immediateCheck') _immediate: boolean = false;
+ @Input('horizontal') _horizontal: boolean = false;
+ @Input('alwaysCallback') _alwaysCallback: boolean = false;
+ @Input()
+ set debounce(value: string | boolean) {
+ this.throttleType = value === '' || !!value ? 'debounce' : 'throttle';
+ }
+
+ private throttleType: string = 'throttle';
+ private disposeScroller: Subscription;
+
+ constructor(
+ private element: ElementRef,
+ private zone: NgZone,
+ private positionResolverFactory: PositionResolverFactory,
+ private scrollRegister: ScrollRegister,
+ private scrollerResolver: ScrollResolver
+ ) {}
+
+ ngOnInit() {
+ if (typeof window !== 'undefined') {
+ const containerElement = this.resolveContainerElement();
+ const positionResolver = this.positionResolverFactory.create({
+ windowElement: containerElement,
+ horizontal: this._horizontal
+ });
+ const options: ScrollRegisterConfig = {
+ container: positionResolver.container,
+ throttleType: this.throttleType,
+ throttleDuration: this._throttle,
+ filterBefore: () => !this._disabled,
+ mergeMap: () => positionResolver.calculatePoints(this.element),
+ scrollHandler: (container: PositionStats) => this.handleOnScroll(container)
+ };
+ this.disposeScroller = this.scrollRegister.attachEvent(options);
+ }
+ }
+
+ handleOnScroll(container: PositionStats) {
+ const scrollResolverConfig = {
+ distance: {
+ down: this._distanceDown,
+ up: this._distanceUp
+ }
+ };
+ const scrollStats: ScrollStats = this.scrollerResolver.getScrollStats(container, scrollResolverConfig);
+ if (this.shouldTriggerEvents(scrollStats.shouldScroll)) {
+ const infiniteScrollEvent: InfiniteScrollEvent = {
+ currentScrollPosition: container.scrolledUntilNow
+ };
+ if (scrollStats.isScrollingDown) {
+ this.onScrollDown(infiniteScrollEvent);
+ } else {
+ this.onScrollUp(infiniteScrollEvent);
+ }
+ }
+ }
+
+ shouldTriggerEvents(shouldScroll: boolean) {
+ return (this._alwaysCallback || shouldScroll) && !this._disabled;
+ }
+
+ ngOnDestroy () {
+ if (this.disposeScroller) {
+ this.disposeScroller.unsubscribe();
+ }
+ }
+
+ onScrollDown(data: InfiniteScrollEvent = { currentScrollPosition: 0 }) {
+ this.zone.run(() => this.scrolled.emit(data));
+ }
+
+ onScrollUp(data: InfiniteScrollEvent = { currentScrollPosition: 0 }) {
+ this.zone.run(() => this.scrolledUp.emit(data));
+ }
+
+ private resolveContainerElement(): any {
+ if (this._container) {
+ return typeof(this._container) === 'string' ? window.document.querySelector(this._container) : this._container;
+ } else {
+ return this.scrollWindow ? window : this.element;
+ }
+ }
+}
diff --git a/src/ngx-infinite-scroll.ts b/src/ngx-infinite-scroll.ts
new file mode 100644
index 00000000..52087060
--- /dev/null
+++ b/src/ngx-infinite-scroll.ts
@@ -0,0 +1,12 @@
+// Public classes.
+// export { SumService } from './services/sum-service';
+// export { ArithmeticModule } from './modules/arithmetic-module';
+
+export { InfiniteScroll } from './modules/infinite-scroll';
+export { PositionResolver, PositionResolverFactory } from './services/position-resolver';
+export { AxisResolver, AxisResolverFactory } from './services/axis-resolver';
+export { ScrollRegister } from './services/scroll-register';
+export { ScrollResolver } from './services/scroll-resolver';
+export { InfiniteScrollModule } from './modules';
+
+export { ContainerRef, InfiniteScrollEvent, PositionElements, PositionStats, ScrollStats, ScrollerConfig} from './models';
\ No newline at end of file
diff --git a/src/services/axis-resolver.ts b/src/services/axis-resolver.ts
new file mode 100644
index 00000000..7792aee0
--- /dev/null
+++ b/src/services/axis-resolver.ts
@@ -0,0 +1,22 @@
+import { Injectable, Inject } from '@angular/core';
+
+@Injectable()
+export class AxisResolverFactory {
+ constructor() { }
+
+ create(vertical: boolean = true) {
+ return new AxisResolver(vertical);
+ }
+}
+
+export class AxisResolver {
+ constructor(private vertical: boolean = true) {
+ }
+ clientHeightKey() { return this.vertical ? 'clientHeight' : 'clientWidth'; }
+ offsetHeightKey() { return this.vertical ? 'offsetHeight' : 'offsetWidth'; }
+ scrollHeightKey() { return this.vertical ? 'scrollHeight' : 'scrollWidth'; }
+ pageYOffsetKey() { return this.vertical ? 'pageYOffset' : 'pageXOffset'; }
+ offsetTopKey() { return this.vertical ? 'offsetTop' : 'offsetLeft'; }
+ scrollTopKey() { return this.vertical ? 'scrollTop' : 'scrollLeft'; }
+ topKey() { return this.vertical ? 'top' : 'left'; }
+}
diff --git a/src/services/position-resolver.ts b/src/services/position-resolver.ts
new file mode 100644
index 00000000..2ee74a5c
--- /dev/null
+++ b/src/services/position-resolver.ts
@@ -0,0 +1,116 @@
+import { Injectable, ElementRef } from '@angular/core';
+import { AxisResolver, AxisResolverFactory } from './axis-resolver';
+import { ContainerRef, PositionElements, PositionStats } from '../models';
+
+@Injectable()
+export class PositionResolverFactory {
+
+ constructor(private axisResolver: AxisResolverFactory) {
+ }
+
+ create (options: PositionElements) {
+ return new PositionResolver(this.axisResolver.create(!options.horizontal), options);
+ }
+}
+
+export class PositionResolver {
+ private documentElement: ContainerRef;
+ private isContainerWindow: boolean;
+ public container: ContainerRef;
+
+ constructor (private axis: AxisResolver, private options: PositionElements) {
+ this.resolveContainer(this.options.windowElement);
+ this.defineContainer(this.options.windowElement);
+ }
+
+ defineContainer(windowElement: ContainerRef) {
+ if (this.resolveContainer(windowElement) || !windowElement.nativeElement) {
+ this.container = windowElement;
+ } else {
+ this.container = windowElement.nativeElement;
+ }
+ return this.container;
+ }
+
+ resolveContainer(windowElement: ContainerRef): boolean {
+ const isContainerWindow = Object.prototype.toString.call(windowElement).includes('Window');
+ this.isContainerWindow = isContainerWindow;
+ return isContainerWindow;
+ }
+
+ getDocumentElement() {
+ return this.isContainerWindow
+ ? this.options.windowElement.document.documentElement
+ : null;
+ }
+
+ calculatePoints (element: ElementRef) {
+ return this.isContainerWindow
+ ? this.calculatePointsForWindow(element)
+ : this.calculatePointsForElement(element);
+ }
+
+ calculatePointsForWindow (element: ElementRef): PositionStats {
+ // container's height
+ const height = this.height(this.container);
+ // scrolled until now / current y point
+ const scrolledUntilNow = height + this.pageYOffset(this.getDocumentElement());
+ // total height / most bottom y point
+ const totalToScroll = this.offsetTop(element.nativeElement) + this.height(element.nativeElement);
+ return { height, scrolledUntilNow, totalToScroll };
+ }
+
+ calculatePointsForElement (element: ElementRef) {
+ let scrollTop = this.axis.scrollTopKey();
+ let scrollHeight = this.axis.scrollHeightKey();
+ const container = this.container;
+
+ const height = this.height(container);
+ // perhaps use this.container.offsetTop instead of 'scrollTop'
+ const scrolledUntilNow = container[scrollTop];
+ let containerTopOffset = 0;
+ const offsetTop = this.offsetTop(container);
+ if (offsetTop !== void 0) {
+ containerTopOffset = offsetTop;
+ }
+ const totalToScroll = container[scrollHeight];
+ return { height, scrolledUntilNow, totalToScroll };
+ }
+
+ private height (elem: any) {
+ let offsetHeight = this.axis.offsetHeightKey();
+ let clientHeight = this.axis.clientHeightKey();
+
+ // elem = elem.nativeElement;
+ if (isNaN(elem[offsetHeight])) {
+ return this.getDocumentElement()[clientHeight];
+ } else {
+ return elem[offsetHeight];
+ }
+ }
+
+ private offsetTop (elem: any) {
+ let top = this.axis.topKey();
+
+ // elem = elem.nativeElement;
+ if (!elem.getBoundingClientRect) { // || elem.css('none')) {
+ return;
+ }
+ return elem.getBoundingClientRect()[top] + this.pageYOffset(elem);
+ }
+
+ pageYOffset (elem: any) {
+ let pageYOffset = this.axis.pageYOffsetKey();
+ let scrollTop = this.axis.scrollTopKey();
+ let offsetTop = this.axis.offsetTopKey();
+
+ // elem = elem.nativeElement;
+ if (isNaN(window[pageYOffset])) {
+ return this.getDocumentElement()[scrollTop];
+ } else if (elem.ownerDocument) {
+ return elem.ownerDocument.defaultView[pageYOffset];
+ } else {
+ return elem[offsetTop];
+ }
+ }
+}
diff --git a/src/services/scroll-register.ts b/src/services/scroll-register.ts
new file mode 100644
index 00000000..b27823f5
--- /dev/null
+++ b/src/services/scroll-register.ts
@@ -0,0 +1,33 @@
+import { ContainerRef } from '../models';
+import { Injectable, ElementRef } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Subscription } from 'rxjs/Subscription';
+import 'rxjs/add/observable/fromEvent';
+import 'rxjs/add/observable/timer';
+import 'rxjs/add/observable/of';
+import 'rxjs/add/operator/debounce';
+import 'rxjs/add/operator/throttle';
+import 'rxjs/add/operator/filter';
+import 'rxjs/add/operator/mergeMap';
+
+
+export interface ScrollRegisterConfig {
+ container: ContainerRef;
+ throttleType: string;
+ throttleDuration: number;
+ filterBefore: Function;
+ mergeMap: Function;
+ scrollHandler: Function;
+}
+
+@Injectable()
+export class ScrollRegister {
+ attachEvent (options: ScrollRegisterConfig): Subscription {
+ const scroller$: Subscription = Observable.fromEvent(options.container, 'scroll')
+ [options.throttleType](() => Observable.timer(options.throttleDuration))
+ .filter(options.filterBefore)
+ .mergeMap((ev: any) => Observable.of(options.mergeMap(ev)))
+ .subscribe(options.scrollHandler);
+ return scroller$;
+ }
+}
diff --git a/src/services/scroll-resolver.ts b/src/services/scroll-resolver.ts
new file mode 100644
index 00000000..01a5db0e
--- /dev/null
+++ b/src/services/scroll-resolver.ts
@@ -0,0 +1,33 @@
+import { PositionStats, ScrollerConfig } from '../models';
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class ScrollResolver {
+ public lastScrollPosition: number = 0;
+
+ shouldScroll (container: PositionStats, config: ScrollerConfig, scrollingDown: boolean) {
+ const distance = config.distance;
+ let remaining: number;
+ let containerBreakpoint: number;
+ if (scrollingDown) {
+ remaining = container.totalToScroll - container.scrolledUntilNow;
+ containerBreakpoint = container.height * distance.down + 1;
+ } else {
+ remaining = container.scrolledUntilNow;
+ containerBreakpoint = container.height * distance.up + 1;
+ }
+ const shouldScroll: boolean = remaining <= containerBreakpoint;
+ this.lastScrollPosition = container.scrolledUntilNow;
+ return shouldScroll;
+ }
+
+ isScrollingDown (container: PositionStats) {
+ return this.lastScrollPosition < container.scrolledUntilNow;
+ }
+
+ getScrollStats (container: PositionStats, config: ScrollerConfig) {
+ const isScrollingDown = this.isScrollingDown(container);
+ const shouldScroll = this.shouldScroll(container, config, isScrollingDown);
+ return { isScrollingDown, shouldScroll };
+ }
+}
diff --git a/src/services/sum-service.ts b/src/services/sum-service.ts
new file mode 100644
index 00000000..157991dd
--- /dev/null
+++ b/src/services/sum-service.ts
@@ -0,0 +1,22 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class SumService {
+
+ /**
+ * Stores the last sum.
+ */
+ public sum: number;
+
+ /**
+ * Calculates the sum.
+ * @param addends Numbers to be added
+ */
+ public calculate(...addends: number[]): void {
+ this.sum = 0;
+ for (let addend of addends) {
+ this.sum += addend;
+ }
+ }
+
+}
diff --git a/tests/modules/infinite-scroll.spec.ts b/tests/modules/infinite-scroll.spec.ts
new file mode 100644
index 00000000..039704b6
--- /dev/null
+++ b/tests/modules/infinite-scroll.spec.ts
@@ -0,0 +1,185 @@
+import {
+ async,
+ inject
+} from '@angular/core/testing';
+import { InfiniteScroll } from '../../src/modules/infinite-scroll';
+import { AxisResolverFactory } from '../../src/services/axis-resolver';
+import { PositionResolverFactory } from '../../src/services/position-resolver';
+import { ScrollRegister } from '../../src/services/scroll-register';
+import { ScrollResolver } from '../../src/services/scroll-resolver';
+
+import { ElementRef, NgZone, SimpleChanges, SimpleChange } from '@angular/core';
+
+describe('Infinite Scroll Directive', () => {
+ // const zone = new NgZone({ enableLongStackTrace: false });
+ let isScrollingDown = true;
+ let zoneSpy: any, scrollResolverSpy: any, scrollRegisterSpy: any, positionResolverSpy: any;
+ const positionFactoryMock: any = {
+ create: () => positionResolverSpy
+ };
+ const createMockElement = () => {
+ const mockedElement: ElementRef = new ElementRef(document.createElement('div'));
+ return mockedElement;
+ };
+ const createInfiniteScroll = (mockedElement?: any) => {
+ mockedElement = mockedElement || createMockElement();
+ return new InfiniteScroll(
+ mockedElement,
+ zoneSpy,
+ positionFactoryMock,
+ scrollRegisterSpy,
+ scrollResolverSpy
+ );
+ };
+
+ beforeEach(() =>{
+ zoneSpy = jasmine.createSpyObj('zone', ['run']);
+ scrollResolverSpy = {
+ getScrollStats: () => {
+ return { shouldScroll: true, isScrollingDown };
+ }
+ };
+ scrollRegisterSpy = jasmine.createSpyObj('register', ['attachEvent'])
+ positionResolverSpy = jasmine.createSpyObj('pos', ['create', 'container']);
+ });
+
+ it('should create an instance of the directive', () => {
+ const actual = createInfiniteScroll();
+ expect(actual).toBeDefined();
+ });
+
+ it('should have default @Input properties values', () => {
+ const directive: any = createInfiniteScroll();
+ const expectedInputs: Object = {
+ _distanceDown: 2,
+ _distanceUp: 1.5,
+ _throttle: 300,
+ scrollWindow: true,
+ _immediate: false,
+ _horizontal: false,
+ _alwaysCallback: false,
+ _disabled: false,
+ _container: null
+ };
+
+ Object.keys(expectedInputs).forEach(input =>
+ expect(directive[input]).toEqual(expectedInputs[input]));
+ });
+
+ it('should trigger the onScrollDown event when scroll has passed _distancedDown', () => {
+ const directive = createInfiniteScroll();
+ const container = {
+ height: 0,
+ scrolledUntilNow: 0,
+ totalToScroll: 0,
+ }
+ spyOn(directive, 'onScrollDown');
+ directive.ngOnInit();
+ directive.handleOnScroll(container)
+ const actual = directive.onScrollDown;
+ expect(actual).toHaveBeenCalled();
+ });
+
+ it('should trigger the onScrollUp event when scroll has passed _distanceUp', () => {
+ const directive = createInfiniteScroll();
+ const container = {
+ height: 0,
+ scrolledUntilNow: 0,
+ totalToScroll: 0,
+ };
+ spyOn(directive, 'onScrollUp');
+ directive.ngOnInit();
+ isScrollingDown = false;
+ directive.handleOnScroll(container);
+ const actual = directive.onScrollUp;
+ expect(actual).toHaveBeenCalled();
+ });
+
+ it('should disable the scroller', () => {
+ const directive = createInfiniteScroll();
+ const container = {
+ height: 0,
+ scrolledUntilNow: 0,
+ totalToScroll: 0,
+ }
+ spyOn(directive, 'onScrollDown');
+ directive.ngOnInit();
+ directive._disabled = true;
+ directive.handleOnScroll(container);
+ const actual = directive.onScrollDown;
+ expect(actual).not.toHaveBeenCalled();
+ });
+
+ describe('resolving container', () => {
+ let directive: InfiniteScroll;
+ let mockedElement: ElementRef;
+ const container = {
+ height: 0,
+ scrolledUntilNow: 0,
+ totalToScroll: 0,
+ };
+
+ beforeEach(() => {
+ mockedElement = createMockElement();
+ directive = createInfiniteScroll(mockedElement);
+ spyOn(positionFactoryMock, 'create').and.callThrough();
+ });
+
+ describe('when container input is defined', () => {
+ describe('when css selector is used', () => {
+ beforeEach(() => {
+ spyOn(document, 'querySelector').and.returnValue(container);
+ directive._container = '.test';
+ directive.ngOnInit();
+ });
+
+ it('should find element in DOM', () => {
+ expect(document.querySelector).toHaveBeenCalledWith('.test');
+ });
+
+ it('should return container', () => {
+ expect(positionFactoryMock.create)
+ .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: container}));
+ });
+ });
+
+ describe('when container is passed directly', () => {
+ beforeEach(() => {
+ directive._container = container;
+ directive.ngOnInit();
+ });
+
+ it('should return container', () => {
+ expect(positionFactoryMock.create)
+ .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: container}));
+ });
+ });
+ });
+
+ describe('when container input is not defined', () => {
+ describe('when scrollWindow is true', () => {
+ beforeEach(() => {
+ directive.scrollWindow = true;
+ directive.ngOnInit();
+ });
+
+ it('should return window', () => {
+ expect(positionFactoryMock.create)
+ .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: window}));
+ });
+ });
+
+ describe('when scrollWindow is false', () => {
+ beforeEach(() => {
+ directive.scrollWindow = false;
+ directive.ngOnInit();
+ });
+
+ it('should return current element', () => {
+ expect(positionFactoryMock.create)
+ .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: mockedElement}));
+ });
+ });
+ });
+ });
+});
diff --git a/tests/services/position-resolver.spec.ts b/tests/services/position-resolver.spec.ts
new file mode 100644
index 00000000..17722718
--- /dev/null
+++ b/tests/services/position-resolver.spec.ts
@@ -0,0 +1,75 @@
+import {
+ async,
+ inject
+} from '@angular/core/testing';
+import { PositionResolver } from '../../src/services/position-resolver';
+import { AxisResolver } from '../../src/services/axis-resolver';
+import { ElementRef } from '@angular/core';
+
+describe('Position Resolver', () => {
+ let mockedElement: ElementRef;
+ let mockedContainer: ElementRef;
+
+ const createMockDom = () => {
+ const container = document.createElement('section');
+ container.setAttribute('style', 'height: 500px; overflow-y: scroll');
+ const el = document.createElement('div');
+ el.setAttribute('style', 'height: 1000px;');
+ container.appendChild(el);
+ mockedElement = new ElementRef(el);
+ mockedContainer = new ElementRef(container);
+ return { element: mockedElement, container: mockedContainer };
+ };
+
+ const createPositionResolver = (element: ElementRef, container: ElementRef) => {
+ const options = {
+ windowElement: element,
+ horizontal: true
+ };
+ const axis: AxisResolver = new AxisResolver();
+ return new PositionResolver(axis, options);
+ };
+
+ beforeEach(() =>{
+
+ });
+
+ it('should create an instance of position resolver', () => {
+ const mockDom = createMockDom();
+ const actual = createPositionResolver(mockDom.element, mockDom.container);
+ expect(actual).toBeDefined();
+ });
+
+ it('should calculate points', () => {
+ const mockDom = createMockDom();
+ const service = createPositionResolver(mockDom.element, mockDom.container);
+ const actual = service.calculatePoints(mockDom.element);
+ expect(actual).toBeDefined();
+ });
+
+ describe('creating instance for non-window element', () => {
+ let service: PositionResolver;
+
+ describe('when nativeElement is present', () => {
+ beforeEach(() => {
+ const mockDom = createMockDom();
+ service = createPositionResolver(mockDom.element, mockDom.container);
+ });
+
+ it('should use container as nativeElement', () => {
+ expect(service.container instanceof HTMLDivElement).toBeTruthy();
+ });
+ });
+
+ describe('when nativeElement is not present', () => {
+ beforeEach(() => {
+ const mockDom = createMockDom();
+ service = createPositionResolver(mockDom.element, mockDom.container.nativeElement);
+ });
+
+ it('should use container as nativeElement', () => {
+ expect(service.container instanceof HTMLDivElement).toBeTruthy();
+ });
+ });
+ });
+});
diff --git a/tests/services/scroll-register.spec.ts b/tests/services/scroll-register.spec.ts
new file mode 100644
index 00000000..08e629d0
--- /dev/null
+++ b/tests/services/scroll-register.spec.ts
@@ -0,0 +1,44 @@
+import { Subscription } from 'rxjs/Rx';
+import {
+ async,
+ inject
+} from '@angular/core/testing';
+import { ScrollRegister, ScrollRegisterConfig } from '../../src/services/scroll-register';
+import { ElementRef } from '@angular/core';
+
+describe('Scroll Regsiter', () => {
+ let mockedElement: ElementRef;
+ let mockedContainer: ElementRef;
+ let scrollRegister: ScrollRegister;
+
+ const createMockDom = () => {
+ const container = document.createElement('section');
+ container.setAttribute('style', 'height: 500px; overflow-y: scroll');
+ const el = document.createElement('div');
+ el.setAttribute('style', 'height: 1000px;');
+ container.appendChild(el);
+ mockedElement = new ElementRef(el);
+ mockedContainer = new ElementRef(container);
+ return { element: mockedElement, container: mockedContainer };
+ };
+
+ beforeEach(() =>{
+ scrollRegister = new ScrollRegister();
+ });
+
+ it('should create a Subscription of scroll observable', () => {
+ const mockDom = createMockDom();
+ const scrollConfig: ScrollRegisterConfig = {
+ container: mockDom.container.nativeElement,
+ filterBefore: () => true,
+ mergeMap: (e: any) => e,
+ scrollHandler: (ev: any) => ev,
+ throttleDuration: 300,
+ throttleType: 'throttle'
+
+ };
+ const scroller$: Subscription = scrollRegister.attachEvent(scrollConfig);
+ const actual = scroller$;
+ expect(actual).toBeDefined();
+ });
+});
diff --git a/tsconfig-build.json b/tsconfig-build.json
new file mode 100644
index 00000000..766a8f25
--- /dev/null
+++ b/tsconfig-build.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "declaration": true,
+ "experimentalDecorators": true,
+ "module": "es2015",
+ "moduleResolution": "node",
+ "outDir": "dist",
+ "rootDir": ".",
+ "sourceMap": true,
+ "inlineSources": true,
+ "target": "es2015",
+ "skipLibCheck": true,
+ "lib": [
+ "es2015",
+ "dom"
+ ]
+ },
+ "files": [
+ "public_api.ts",
+ "node_modules/zone.js/dist/zone.js.d.ts"
+ ],
+ "angularCompilerOptions": {
+ "skipTemplateCodegen": true,
+ "annotateForClosureCompiler": true,
+ "strictMetadataEmit": true,
+ "flatModuleOutFile": "index.js",
+ "flatModuleId": "angular-library-starter"
+ }
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 65877efd..dfd43e3e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,47 +1,27 @@
{
- "compilerOptions": {
- "noImplicitAny": true,
- "module": "commonjs",
- "target": "es5",
- "emitDecoratorMetadata": true,
- "experimentalDecorators": true,
- "inlineSourceMap": true,
- "inlineSources": true,
- "declaration": true,
- "suppressImplicitAnyIndexErrors": true,
- "moduleResolution": "node",
- "lib": [
- "dom",
- "es6"
- ],
- "types": [
- "jasmine",
- "node"
+ "compilerOptions": {
+ "baseUrl": ".",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "noImplicitAny": false,
+ "noFallthroughCasesInSwitch": true,
+ "sourceMap": true,
+ "rootDir": ".",
+ "inlineSources": true,
+ "lib": [
+ "es6",
+ "dom"
+ ],
+ "target": "es5",
+ "skipLibCheck": true,
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "exclude": [
+ "node_modules"
]
- },
- "exclude": [
- "node_modules",
- "bundles"
- ],
- "awesomeTypescriptLoaderOptions": {
- "forkChecker": true,
- "useWebpackText": true
- },
- "files": [
- "./angular2-infinite-scroll.ts",
- "./src/infinite-scroll.ts",
- "./src/infinite-scroll.spec.ts",
- "./src/axis-resolver.ts",
- "./src/axis-resolver.spec.ts",
- "./src/position-resolver.ts",
- "./src/position-resolver.spec.ts",
- "./src/scroll-resolver.ts",
- "./src/scroll-register.ts",
- "./src/models.ts",
- "./src/index.ts"
- ],
- "angularCompilerOptions": {
- "genDir": "./src/ngfactory",
- "debug": false
- }
-}
+}
\ No newline at end of file
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 00000000..c0d7a7bf
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,55 @@
+{
+ "extends": "tslint:recommended",
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ [
+ "dir-prefix1",
+ "dir-prefix2"
+ ],
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ [
+ "cmp-prefix1",
+ "cmp-prefix2"
+ ],
+ "kebab-case"
+ ],
+ "use-input-property-decorator": true,
+ "use-output-property-decorator": true,
+ "use-host-property-decorator": true,
+ "no-attribute-parameter-decorator": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "no-forward-ref": true,
+ "use-life-cycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": [
+ true,
+ "Component"
+ ],
+ "directive-class-suffix": [
+ true,
+ "Directive"
+ ],
+ "templates-use-public": true,
+ "no-access-missing-member": true,
+ "invoke-injectable": true,
+ "ordered-imports": [
+ false
+ ],
+ "quotemark": [
+ false
+ ],
+ "trailing-comma": [
+ false
+ ]
+ }
+}
\ No newline at end of file