-
-
Save iansinnott/3d0ba1e9edc3e6967bc51da7020926b0 to your computer and use it in GitHub Desktop.
/** | |
* Read the text contents of a File or Blob using the FileReader interface. | |
* This is an async interface so it makes sense to handle it with Rx. | |
* @param {blob} File | Blob | |
* @return Observable<string> | |
*/ | |
const readFile = (blob) => Observable.create(obs => { | |
if (!(blob instanceof Blob)) { | |
obs.error(new Error('`blob` must be an instance of File or Blob.')); | |
return; | |
} | |
const reader = new FileReader(); | |
reader.onerror = err => obs.error(err); | |
reader.onabort = err => obs.error(err); | |
reader.onload = () => obs.next(reader.result); | |
reader.onloadend = () => obs.complete(); | |
return reader.readAsText(blob); | |
}); |
Thank you for this. Concise and solved my problem. Also, demonstrates to me how to convert other types of event handlers into Observables. I modified the code slightly to fail earlier on the Blob instanceof test. Not sure if this is an equivalent situation or a matter of style. In this case, we only create the Observable if the blob is the correct type, otherwise we return the throwError Observable.
const readFile = (blob: Blob): Observable<string> =>
{
if (!(blob instanceof Blob)) {
return throwError(new Error('`blob` must be an instance of File or Blob.'));
}
return Observable.create(obs => {
const reader = new FileReader();
reader.onerror = err => obs.error(err);
reader.onabort = err => obs.error(err);
reader.onload = () => obs.next(reader.result);
reader.onloadend = () => obs.complete();
return reader.readAsText(blob);
});
}
One question. How would one test this function/method? Especially if one uses TypeScript. BecauseFileReader
interface for some reason is not part of Window
interface. I am mentioning that, because it would seem only logical, that FileReader
should be mocked out for testing purpose.
If you wanted to mock out the file reader interface I would suggest simply passing in a constructor as a second argument with a default so that you can ignore the second argument outside of testing.
const readFile = (blob, Reader = FileReader) => { /* ... */ }
Sounds interesting. Will try it out. Thanks.
If you were using Jasmine, could you use spyOn
to mock FileReader
?
Not sure, but using the approach above you could pass in anything you want in place of file reader so I think it would solve this use case. Here's an updated example using typescript and the new Observable
syntax from Rxjs 7.
const readFile = (blob: Blob, reader: FileReader = new FileReader()) => new Observable(obs => {
if (!(blob instanceof Blob)) {
obs.error(new Error('`blob` must be an instance of File or Blob.'));
return;
}
reader.onerror = err => obs.error(err);
reader.onabort = err => obs.error(err);
reader.onload = () => obs.next(reader.result);
reader.onloadend = () => obs.complete();
return reader.readAsText(blob);
});
Then in your test code something like this. Not sure what you would want here but that would depend on your test suite.
class MockReader { /* ... */ }
const reader = new MockReader();
await readFile(someBlob, reader).toPromise();
expect(reader.readAsText).toHaveBeenCalled();
I think this should also call reader.abort()
when the Observable is unsubscribed from.
here is a simple implementation of opening an image in browser using RxJS 7.1
<input type='file' id='file-input'>
<br>
<img src="" id='image-preview' width='200px'>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.1.0/rxjs.umd.js"></script>
<script>
const { fromEvent, Observable } = rxjs;
const { flatMap } = rxjs.operators;
var fileSelectStream = fromEvent(document.getElementById('file-input'), 'change');
function createRxObservable(fileNameEvent){
var file = fileNameEvent.target.files[0];
const readFile_rx_observable = Observable.create(
function(subscriber) {
var reader = new FileReader();
reader.onload = function(e) {
file.content = e.target.result;
subscriber.next(file);
};
reader.readAsDataURL(file);
}
);
return readFile_rx_observable;
}
var fileReadStream = fileSelectStream
.pipe(flatMap(createRxObservable));
fileReadStream.subscribe((file)=>{
document.getElementById('image-preview').src = file.content;
});
</script>
a working fiddle is here: https://jsfiddle.net/MuazSamli/5urd9bcq/25/
Hi
Thanks for your function :)
For those who wants auto completion when they use this function.