-
-
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.