(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.
e.target.result as string.