AngularJS module that enables iScroll 5 functionality, wrapping it in an easy-to-use directive
Install the angular-iscroll NPM package
npm install --save angular-iscroll
Install through Yarn
yarn add angular-iscroll
Or, to check out a development version, start by cloning the repository, by
git clone [email protected]:mtr/angular-iscroll.git
If you don't use yarn
you may run npm run-script [command]
instead of yarn [command]
. So, to install the necessary dependencies:
cd angular-iscroll/
yarn install
yarn build # or `npm run-script build`
After that, you should have a dist
directory with a subdirectory named lib
:
dist/
└── lib
├── angular-iscroll.js
├── angular-iscroll.js.gz
└── scss
└── _iscroll.scss
To rebuild the library, run
yarn build # or `yarn watch`
You may have a look at core-layout (GitHub repo), an Angular demo app that shows how you can use the iscroll
directive in a responsive-design web-app with support for both drawers (slide-out menus) and modals. For example, the demo shows how to handle DOM content generated dynamically through ngRepeat.
In the following, IScroll
(with capital 'I' and 'S') refers to instances
of the iScroll Javascript library that this package provides an AngularJS wrapper for.
The main usage pattern for angular-iscroll
is to define a dependency on the angular-iscroll
module in your AngularJS app. For example:
angular.module('myApp', ['angular-iscroll']);
or, in a Browserify-based code base:
angular.module('myApp', [require('angular-iscroll').name]);
The angular-iscroll
module includes both a directive, iscroll
, and a service, iScrollService
, which gives you access to and control over a shared, global state of whether to enable, disable, or refresh the IScroll
instances for each iscroll
directive instance.
Next, to use the directive, you should set up your HTML template like
…
<body ng-controller="MyAppController as app"
ng-class="{
'iscroll-on': app.iScrollState.useIScroll,
'iscroll-off': !app.iScrollState.useIScroll
}">
<div class="iscroll-wrapper" iscroll>
<div class="iscroll-scroller">
</div>
</div>
…
Let me explain the essential parts of that HTML example. First of all, the iscroll
directive is an attribute of an element belonging to the iscroll-wrapper
class, which wraps an element of the iscroll-scroller
class. Those two classes are defined in the SASS file dist/lib/scss/_iscroll.scss, but they don't have any meaning unless they occur inside an iscroll-on
class; and that's where the shared, global state from iScrollService comes in. The controller, MyAppController
, in the above example exposes the state variable shared by iScrollService in its scope
function MyAppController(iScrollService) {
var vm = this; // Use 'controller as' syntax
vm.iScrollState = iScrollService.state;
}
thereby providing a way to globally change the meaning of the iscroll-wrapper
+ iscroll-scroller
combination. Please note: To get more info about the "controller as" syntax, you might enjoy John Papa's AngularJS Style Guide.
Furthermore, the global iScroll state exposed by the service should be changed through the service's enable([signalOnly])
, disable([signalOnly])
, and toggle([signalOnly])
methods, where each method will change the state accordingly, and then emit a corresponding signal from $rootScope
that gets picked up and handled by the available angular-iscroll
directive instances. If the signalOnly
flag is true
, then the state is not changed by the service method, but the signal is sent nonetheless. If the directives receive an iscroll:disabled
signal, they will destroy any existing IScroll
instances, and if they receive an iscroll:enabled
signal, they will create a new IScroll
instances per directive instance if it doesn't already exist.
It should also be noted that during instantiation, in the directive's post-link phase, the iscroll
directive will check the iScrollService
's useIScroll
state to decide whether or not it will create an actual IScroll
instance. Consequently, if you would like to create an AngularJS solution that uses iScroll only on, for example, iOS devices, you should determine the current browser type early, probably inside the app controller's configuration block, and set the service's useIscroll
state accordingly. Please note that angular-iscroll
does not contain any code to detect which browser or platform it is currently running on, which is a separate, complex task better solved by specialized libraries, like platform.js.
If you want access to a scope's IScroll
instance, you can supply an optional
iscroll-instance
attribute when applying the iscroll
directive, like
…
<div class="iscroll-wrapper" iscroll iscroll-instance="instance">
<div class="iscroll-scroller">
</div>
</div>
…
That way, the scope's instance
variable will hold a reference to the actual
IScroll
instance, so you can access the IScroll instance's own API, for
example to define custom events or
access its scroller info.
I've designed this module so that it should be easy to configure. First of all, you can supply per-instance options, both for IScroll
and the directive itself, when you apply the directive. For example
<div iscroll="{mouseWheel: true, momentum: true, refreshInterval: 500}">…</div>
would pass along the options {mouseWheel: true, momentum: true}
to IScroll
, while the directive-specific configuration parameter, {refreshInterval: 500}
, is only interpreted by the directive. Any config option not recognized as a directive-specific option, will be forwarded to IScroll
.
There are lots of configuration options for IScroll itself; those are best documented by iScroll.
The directive provides two configuration options:
-
asyncRefreshDelay
(default0
): defines the delay, in ms, before the directive asynchronously performs an IScroll.refresh(). Iffalse
, then no async refresh is performed. This can come in handy when you need to wait for the DOM to be rendered beforeIScroll
can know the size of its scrolling area. -
refreshInterval
(defaultfalse
): a delay, in ms, between each periodic iScroll.refresh(). Iffalse
, then no periodic refresh is performed. This functionality can be handy in complex applications, where it might be difficult to decide wheniScrollService.refresh()
should be called, and a periodic call toIScroll.refresh()
, for example every 500 ms, might provide a smooth user experience. To avoid scroll stuttering caused by calls to refresh during an ongoing scroll operation, theangular-iscroll
directive preventsrefresh()
calls if IScroll is currently performing a scroll operation. -
invokeApply
(defaultfalse
, since version 2.0.0): whether or not to invoke AngularJS'$apply()
(and thereby$digest()
) cycle on every refresh, as determined byasyncRefreshDelay
orrefreshInterval
. Whenfalse
, it will not invoke model dirty checking on every call toIScroll.refresh()
. This can result in huge performance gain ifrefreshInterval
is set to a low value (for example 500 ms). Example usage:iScrollServiceProvider.configureDefaults({ iscroll: { invokeApply:false } });
To test it, you can paste this code to your app
run
block:/** * This code measures `$digest()` performance * by logging digest times to the console. */ var $oldDigest = $rootScope.$digest; var $newDigest = function() { console.time("$digest"); $oldDigest.apply($rootScope); console.timeEnd("$digest"); }; $rootScope.$digest = $newDigest;
With
invokeApply = true
andrefreshInterval = 500
you'll see that digest is run every 500ms. WithinvokeApply = false
andrefreshInterval = 500
you'll see that digest is not invoked byangular-iscroll
.Thanks to DinkoMiletic for implementing this optimization.
The iscroll
directive gets its default configuration from the iScrollService
. To provide a way to easily, globally configure the defaults for all iscroll
instances, the module defines an iScrollServiceProvider
which can be injected into the app controller's configuration block which is guaranteed to run before the controller is used anywhere. For example:
/* @ngInject */
function _config(iScrollServiceProvider) {
// Supply a default configuration object, eg:
iScrollServiceProvider.configureDefaults({
iScroll: {
// Passed through to the iScroll library
scrollbars: true,
fadeScrollbars: true
},
directive: {
// Interpreted by the directive
refreshInterval: 500
}
});
}
angular
.module('myApp', ['angular-iscroll'])
.config(_config);
The configuration you provide this way will serve as the updated global default for all iscroll
directive instances.
Please note that the above example relies on ng-annotate for adding AngularJS dependency-injection annotations during builds, as indicated by the /* @ngInject */
comment.
Thanks to a generous “free for Open Source” sponsorship from BrowserStack I've been able to test core-layout, and thereby angular-iscroll, with a plethora of devices and browsers. The following browsers and devices has been tested successfully:
- Desktop
- Chrome 16–48 on OS X 10.10
- Chrome 48 on Ubuntu 14.04
- Firefox 6.0–43.0 on OS X 10.10
- Firefox 43.0.4 on Ubuntu 14.04
- Internet Explorer 9, 10, and 11 on Windows 7
- Internet Explorer Edge 12 on Windows 10
- Opera 12.12–34 on OS X 10.10
- Safari 5.1.7 on Windows 10
- Safari 8.0.8 on OS X 10.10.5
- Yandex 14.12 on OS X 10.10
- Mobile
- Android Browser 4.0 with Android 4.0.4 on Samsung Galaxy Note 10.1
- Android Browser 4.0 with Android 4.1.2 on Samsung Galaxy S3
- Chrome Mobile 45 with Android 5.0.2 on Samsung Galaxy S6
- Chrome Mobile 45 with Android 4.4 on Google Nexus 5
- Safari 4.0.5 with iOS 4.0.1 on iPhone 4
- Safari 5.0.2 with iOS 4.3.2 on iPad 2
- Safari 5.1 with iOS 5.0 on iPad 2
- Safari 5.1 with iOS 5.1 on iPhone 4S
- Safari 6.0 with iOS 6.0 on iPhone 5
- Safari 7.0 with iOS 7.0.4 on iPad Air
- Safari 8.0 with iOS 8.1.1 on iPad Air 2
- Safari 9.0 with iOS 9.0.2 on iPhone 6S
- IE Mobile 11.0 with Windows Phone 8.1 on Nokia Lumia 520, 925, and 930
During testing, the core-layout demo broke in the following browsers:
- Firefox 3.6, 4, and 5 on OS X 10.10
- Internet Explorer 8 on Windows 7 (fails during jQuery version 2.2.0 initialization):
Error message:
// Use the handy event callback document.addEventListener( "DOMContentLoaded", completed );
Object doesn't support this property or method
. - Safari 3 with iOS 3 on iPhone 3GS
This does not necessarily mean that angular-iscroll
itself breaks in the same browsers, but the demo code did.