angular-pouchdb
AngularJS v1.x wrapper for PouchDB
A lightweight AngularJS (v1.x) service for PouchDB that:
- Wraps Pouch's methods with
$q
- Makes Angular aware of asynchronous updates
Disclaimer: angular-pouchdb works by monkey patching PouchDB's public
API. Your milage may vary.
Why?
Since PouchDB is asynchronous, you will often need to call $scope.$apply()
before changes are reflected on the UI. For example:
angular.controller('MyCtrl', function($scope, $window) {
var db = $window.PouchDB('db');
db.get('id')
.then(function(res) {
$scope.one = res;
});
var db2 = $window.PouchDB('db2');
db.get('id')
.then(function(res) {
$scope.$apply(function() {
$scope.two = res;
});
});
});
Writing $scope.$apply
each time is laborious and we haven't even mentioned
exception handling or $digest already in progress
errors.
angular-pouchdb handles $scope.$apply
for you by wrapping PouchDB's promises
with $q
. You can then use its promises as you would with any Angular promise,
including the .finally
method (not in the Promises A+ spec).
angular.controller('MyCtrl', function($scope, pouchDB) {
var db = pouchDB('db');
db.get('id')
.then(function(res) {
$scope.one = res;
})
.catch(function(err) {
$scope.err = err;
})
.finally(function() {
$scope.got = true;
});
});
Put another way, angular-pouchdb is not required to integrate PouchDB and
AngularJS; they can and do happily work together without it. However,
angular-pouchdb makes it more convenient to do so.
Usage
-
Install angular-pouchdb
via Bower:
bower install --save angular-pouchdb
-
Add pouchdb
as a module dependency:
angular.module('app', ['pouchdb']);
-
Inject the pouchDB
service in your app:
angular.service('service', function(pouchDB) {
var db = pouchDB('name');
});
From then on, PouchDB's standard promises API applies. For example:
angular.controller('MainCtrl', function($log, $scope, pouchDB) {
var db = pouchDB('dbname');
var doc = { name: 'David' };
function error(err) {
$log.error(err);
}
function get(res) {
if (!res.ok) {
return error(res);
}
return db.get(res.id);
}
function bind(res) {
$scope.doc = res;
}
db.post(doc)
.then(get)
.then(bind)
.catch(error);
});
See examples for further usage examples.
Event emitters
angular-pouchdb decorates PouchDB event emitters (such as those used by
replicate.{to,from}
) with a .$promise
property to make them more useful
within Angular apps, per the following mapping:
Event | Deferred method |
---|
change | .notify |
paused | .notify |
complete | .resolve |
reject | .reject |
For example:
var db = pouchDB('test');
db.replicate.to('https://couch.example.com/remote').$promise
.then(null, null, function(progress) {
console.log('replication status', progress);
})
.then(function(result) {
console.log('replication resolved with', result);
})
.catch(function(reason) {
console.error('replication failed with', reason);
})
.finally(function() {
console.log('done');
});
Options
pouchDBProvider.methods
A hash of pouchDBMethod: decorator
pairs, with arbitrary nesting. Defaults to
POUCHDB_METHODS (a constant mapping PouchDB's core API).
Example:
pouchDBProvider.methods = {
get: 'qify',
replicate: 'replicate'
};
pouchDBDecorators
A service containing decorator functions used to wrap PouchDB's. By default,
this includes qify
, eventEmitter
and replicate
.
Since they're contained in a service, they can be substituted per standard
dependency injection semantics, or reused outside of angular-pouchdb.
FAQ
Does this work with PouchDB plugins?
angular-pouchdb only wraps PouchDB's core API by default. If you need to wrap
other methods (for example, one exposed by a PouchDB plugin), there are (at
least) two strategies:
If the method exists synchronously, add the method name to
pouchDBProvider.methods
in an angular.config
block, for example:
.config(function(pouchDBProvider, POUCHDB_METHODS) {
var authMethods = {
login: 'qify',
logout: 'qify',
getUser: 'qify'
};
pouchDBProvider.methods = angular.extend({}, POUCHDB_METHODS, authMethods);
})
If the method is added after instantiation asynchronously (perhaps via
a promise), manually apply a decorator to the instance, for example:
.controller('myCtrl', function(pouchDB, pouchDBDecorators) {
var db = pouchDB('db');
db.find = pouchDBDecorators.qify(db.find);
});
How can I debug this?
Debugging angular-pouchdb in a console can be done by first retrieving the
injector and calling the pouchDB
service as normal, e.g.:
var pouchDB = angular.element(document.body).injector().get('pouchDB');
var db = pouchDB('mydb');
db.get('id').then();
For further tips and tricks, see CouchDB Best Practices.
Can this be used with Browserify?
Yes! For example:
require('angular').module('app', [
require('angular-pouchdb')
]);
Can this be used with webpack?
Yes, though you need to use expose-loader to ensure PouchDB is available as
a global, for example:
require('expose?PouchDB!pouchdb');
Why do promises timeout in my test suite?
Note: some (or all) parts of this section may be incorrect or misleading.
Your input is welcome.
In short, AngularJS uses a different task scheduler than native promises.
Promises can be implemented differently. PouchDB uses native (A+-compliant)
promises (or lie in environments without native support). Native promises
are scheduled using "the microtask queue". AngularJS uses its own promise
implementation; $q
, which are scheduled via $evalAsync
.
During normal use, PouchDB's (wrapped) promise is resolved correctly. However
during testing, suites that use ngMock
(angular-mocks
) often unexpectedly
timeout.
Typically, $rootScope.$apply()
is used to propagate promise resolution in
asynchronous tests. This triggers a digest cycle, which in turn flushes
Angular's asyncQueue
. Whilst this resolves $q
promises, it does not resolve
PouchDB's native promises, hence causing the test runner (e.g. Karma) to
timeout.
Until Angular's promise implementation is decoupled from its digest cycle and/or
Angular-specific implementations can be swapped out with their native
equivalents, there are a few known workarounds:
Do not use ngMock
ngMock
modifies Angular's deferred implementation in order to support writing
tests in a synchronous manner. Arguably, this simplifies control flow, but comes
at the cost of making $q
-wrapped promises difficult to test.
One workaround (and the one that angular-pouchdb
currently uses) is to not
use ngMock
and manually handle $injector
, for example:
describe('Working $q.when tests', function() {
var pouchdb;
beforeEach(function() {
var $injector = angular.injector(['ng', 'test']);
var pouchDB = $injector.get('pouchdb');
pouchdb = pouchDB('db');
});
it('should resolve a promise', function(done) {
pouchdb.info()
.then(function(info) {
expect(info).toBeDefined();
})
.finally(done);
});
});
This preserves "normal" promise resolution behaviour, but will not suit all
scenarios, such as when you need the additional introspection/async control
features ngMock
provides e.g. $httpBackend.flush
.
Spam $rootScope.$apply
Calling $rootScope.$apply
in quick succession to cause a near-continuous
digest cycle forces promise resolution. This appears to be due to tight coupling
between Angular's promises and its digest cycle.
it('should wrap destroy', function(done) {
var interval = $window.setInterval($rootScope.$apply.bind());
db.destroy()
.then(shouldBeOK)
.then($window.clearInterval.bind(null, interval))
.catch(shouldNotBeCalled)
.finally(done);
})
Note, this is likely to significantly decrease your test's performance.
Does this work with Angular v2?
No and it doesn't need to! Angular v2's concept of change detection completely
differs to Angular v1's; the digest cycle, $scope.$apply
and friends are no
more. Just use PouchDB directly.
Authors
License
Released under the MIT License.