In the interest of knowing-what-I'm-talking-about-before-talking-about-it, I figured I'd shut up about Promises and get really stuck into using Observables to give them a fair shake (you'll get the pun shortly)
Wanted to share my experience on a side-project I'm working on, as a) i think its super-interesting and b) represents a use-case for angular2 data I hadn't really considered.
I'm currently helping to build a concussion-sensing mouthguard (
https://www.fitguard.me/) - I'm doing the firmware, companion app, and cloud backend for data aggregation. The device -> app link happens over Bluetooth Low Energy (BLE), and the app is (currently) built with Ionic in ng1, using ng2 DI to build the "SDK" (basically, I declare an injector and wrap it in an ng1 provider - see
http://plnkr.co/edit/jwBMKG?p=info for that concept)
If you've not used BLE before, it's quite an interesting protocol - as opposed to regular Bluetooth, which uses serial-style communication, BLE uses a much simpler to reason about model :
Two main roles:
- A Central (usually this is the app)
- A Peripheral (usually this is the widget/device/sensor)
A peripheral exposes a GATT Server, which is more or less a "database" running on the device. For example:
Our device exposes several Services - basically a logical grouping - BatteryStatus or Accelerometer would be examples of services exposed by our device.
A service exposes one-or-many Characteristics - these are effectively "properties", and a characteristic exposes one or many Descriptors - which describe (duh) the Characteristics.
Individual Characteristics can typically be:
- read / queried by a central (eg - the app reads the current battery level from the device)
- written by a central (eg, the app might set the device's name from "BLEDevice" to "Rob's BLE Device")
- most interesting / relevant, a Central can subscribe to a characteristic, and be notified when it changes.
So - my Angular app uses a BLE Cordova plugin to handle all this.
If you've not used cordova much, the basic idea is you have native code (Obj-C or Java) talking to native API's (like the BLE functionality), and then cordova exposes a single bridge method, available at window.cordova.exec() from Javascript.
The standard Cordova interface is :
cordova.exec(onSuccess, onError, pluginName, pluginMethod, ...parameters);
Previously, I've typically wrapped that API in a Promise (this is what Ionic's ng-cordova project does), which works great for atomic things, like writing a property:
const SENSOR_ENABLE = 0x01; //yay ES6!
const SENOR_DISABLE = 0x00;
writeCharacteristicValue(characteristicID, value){
return new Promise((resolve, reject) => {
cordova.exec(resolve, reject, 'BLE', 'writeCharacteristicValue', [characteristicID, value]);
});
}
BLEPlugin.writeCharacteristicValue('accelerometerEnabled', SENSOR_ENABLE).then(() => ....);
But doesn't work so well for things like scanning for devices, where the onSuccess callback might get called multiple times:
scanForDevices(timeout){
let discoveredDevices = [];
let onDeviceFound = (device) => discoveredDevices.push(device);
let scanForTime = return new Promise((resolve, reject) => {
cordova.exec(onDeviceFound, reject, 'BLE', 'startScan');
//gross
setTimeout(() => resolve(discoveredDevices),timeout);
});
return scanForTime.then((discoveredDevices) => {
cordova.exec(null, null, 'BLE', 'stopScan');
return discoveredDevices;
});
}
BLEPlugin.scanForDevices(3000).then((devices) => { ... });
Clearly, this is better served by an observable:
scanForDevices(){
return new Rx.Observable.create((observer) => {
const onDeviceFound = device => observer.onNext(device);
const onError = scanError => observer.onError(scanError);
cordova.exec(onDeviceFound, onError, 'BLE', 'startScan');
//this is SO much better(!)
return () => {
cordova.exec(null, null, 'BLE', 'stopScan');
}
});
}
Which lets you do some awesome stuff in a ridiculously simple fashion
//scan for devices for 3 seconds and only give me distinct ones
let deviceScan = BLEManager.scanForDevices().distinct((device) => device.address).takeUntil(Rx.Observable.timeout(3000));
deviceScan.subscribe((device) => {
console.log('found device', device);
});
//or stop early and it cleans... itself... up!
deviceScan.dispose();
And it gets *really* interesting when you start listening to sensors (this is just psuedo, excuse any foibles)
//enable notification at 50hz (20ms)
let accStream = remoteDevice.Accelerometer.enableNotification(20);
//turn them into Vectors (or whatever)
let readings = accStream
.map((sensorReading) => new Vector(sensorReading));
//maybe run some impact detection magic?
let impactDetector = MySuperSecretAlgorithm.impactThreshold(10) // in g's
let impactStream = readings.subscribe(impactDetector);
//stream impacts to da cloud
let cloudLogger = Rx.DOM.fromWebSocket('http://datacloud');
impactStream.subscribe(cloudLogger);
And holy cow, how easy is it to mock out my remote sensor? This easy...
let fakeAccelerometerStream = generateFakeReading().interval(20);
Really super awesome stuff, consider me convinced on this one! That said, there was precious little info out there on doing this sort of thing. Given Angular2's mobile focus, this is one of the places I feel like it's clear to see the advantages of an Observable powered angular, and it would be worth documenting the hell out of these sorts of APIs. Presumably NativeScript et al exposes a similar type of interface (React Native Plugins work this way too, more or less).
Discuss?