3

Getting a TS error in line 15 particularly in e.target.result.

Argument type string | ArrayBuffer is not assignable to parameter type string  Type ArrayBuffer is not assignable to type string

let fileTag = document.getElementById("filetag"),
    preview = document.getElementById("preview");

fileTag.addEventListener("change", function() {
    changeImage(this);
});

function changeImage(input) {
    let reader;

    if (input.files && input.files[0]) {
        reader = new FileReader();

        reader.onload = function(e) {
            preview.setAttribute('src', e.target.result);
        }

        reader.readAsDataURL(input.files[0]);
    }
}

added HTML

<div class="img-container">
<input type="file" id="filetag">
<img src="" class="profImage" id="preview" alt="profilePic">
</div>
4
  • 1
    Typescript type system is not powerful enough to express this kind of typing, therefore you'll have to cast e.target.result as string. Commented Mar 25, 2021 at 21:00
  • 1
    In JavaScript everything works perfectly as you can see it here - jsfiddle.net/s7e1uhx4 Commented Mar 25, 2021 at 21:01
  • @georg can you give me an example with some more code? Commented Mar 25, 2021 at 21:01
  • @cypho my editor gives me the error and when i run it on localhost the image doesn't show up. Commented Mar 25, 2021 at 21:04

2 Answers 2

5

(Independently of the actual answer, consider using URL.createObjectURL instead of converting the File to a data: URI; it will save you some resources on spurious string conversions.)


The problem is that type checking works locally.

let preview = document.getElementById("preview");
let reader = new FileReader();

reader.onload = function(e) {
    preview.setAttribute('src', e.target.result);
}

Based on surrounding context, TypeScript knows that preview is a DOM node, and therefore preview.setAttribute expects two string arguments. It also knows that e.target is a FileReader, and therefore its property result may be either a string or an ArrayBuffer. Which one it is depends on the method that was earlier called on the FileReader, but this information is hard to express in a type system and is not available within the event handler anyway. For all the type checker knows, the event handler may have been invoked after readAsArrayBuffer was called somewhere far, far away on the same FileReader object.

Since in this case you know better than the type checker, you can use a type assertion to convince it that the value is indeed a string:

reader.onload = function(e) {
    preview.setAttribute('src', e.target.result as string);
}

If you don’t want to litter your code with type assertions everywhere, consider wrapping your code in an abstraction that is easier to type check, for example like this:

function readFileAsDataURL(file): Promise<string> {
    return new Promise((accept, reject) => {
        const reader = new FileReader();
        reader.onload = function (ev) {
            accept(ev.target.result as string);
        };
        /* XXX: rejecting with an event is rather unorthodox */
        reader.onabort = reader.onerror = function (ev) {
            reject(ev);
        }
        reader.readAsDataURL(file);
    });
}

async function changeImage(input) {
    preview.setAttribute('src', await readFileAsDataURL(input.files[0]));
}

Now if you remember to use readFileAsDataURL everywhere in your code instead of constructing FileReaders directly, the only place where a type assertion will be required is inside that one function.

Sign up to request clarification or add additional context in comments.

Comments

-1

You might be able to do:

function ab2str(buf: ArrayBuffer): string {
    return String.fromCharCode.apply(null, new Uint16Array(buf));
}
// ...
reader.readAsDataURL(typeof input.files[0] === 'string' ? input.files[0] : ab2str(input.files[0]))

Or, if you are positive it will always be a string:

reader.readAsDataURL(input.files[0] as string);

basically if it's a string, just use it, but if it's an ArrayBuffer, first convert it to a string. Not sure if typescript will be able to follow that though.

https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

Otherwise, just do:

preview.setAttribute('src', e.target.result as string);

again, assuming you are positive it will actually always be a string

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.