I am struggling to understand and make this code work properly. As you can see it is an Rxjs service observable.
I made an workaround with setTimeout with the attempt to wait until resize is complete. Otherwise, it results in an empty fileBlobArray.
I have studied and tried some such as promise.all and async-await with no success.
Thanks for any help.
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class resizeService {
constructor() { }
resizeImage(files: FileList, maxWidth: number, maxHeight: number): Observable<any> {
let resizedFileSubject = new Subject();
let fileBlobArray: Blob[] = [];
Array.from(files).map((file, i, { length }) => {
let image: HTMLImageElement = new Image();
image.src = URL.createObjectURL(file);
image.onload = async () => {
let width = image.width;
let height = image.height;
let canvas = document.createElement('canvas');
let ctx: any = canvas.getContext("2d");
let newHeight;
let newWidth;
const ratio = width / height;
// Calculate aspect ratio
if (width > height) {
newWidth = maxHeight * ratio;
newHeight = maxHeight;
} else {
newWidth = maxWidth * ratio;
newHeight = maxWidth;
}
canvas.width = newWidth;
canvas.height = newHeight;
// Draw image on canvas
ctx.drawImage(image, 0, 0, newWidth, newHeight);
fileBlobArray.push(await this.b64ToBlob(canvas.toDataURL("image/jpeg")));
// Detect end of loop
if (i 1 === length) {
// Wait to get async data - Workaround :(
// setTimeout(() => {
resizedFileSubject.next(fileBlobArray);
// console.log('next: ', fileBlobArray)
// }, 1000);
}
}
});
return resizedFileSubject.asObservable();
}
/**
* Convert BASE64 to BLOB
* @param base64Image Pass Base64 image data to convert into the BLOB
*/
private async b64ToBlob(base64Image: string) {
const parts = base64Image.split(';base64,');
const imageType = parts[0].split(':')[1];
const decodedData = window.atob(parts[1]);
const uInt8Array = new Uint8Array(decodedData.length);
for (let i = 0; i < decodedData.length; i) {
uInt8Array[i] = decodedData.charCodeAt(i);
}
return new Blob([uInt8Array], { type: imageType });
}
}
CodePudding user response:
I would recommend researching the basics of async JS/TS in this order.
- Creating and returning a Promise.
- Promise.all()
- Async / await
- RxJS
Considering your sample code, you don't really need RxJS to fulfill this requirement. The purpose of your service method is to return an array of blobs based on the array of images being passed in the parameters.
Avoid sprinkling async/await in your code until you understand the basics of promises as mentioned above. Currently all of your async/await keywords aren't doing anything because there are no promises in your code.
Looking purely at the requirements, you want to return a new blob when the onload event triggers on a single file. To do this, you need to create a promise that resolves when the onload function is invoked. For simplicity sake, I moved your onload function to a separate service method called handleOnLoad().
return new Promise((res, rej) => {
let image: HTMLImageElement = new Image();
image.src = URL.createObjectURL(file);
image.onload = () => res(this.handleOnLoad(image, maxWidth, maxHeight));
});
Now your Array.from(files).map() can return an array of these promises. One for each file in the array. You can now wrap this array inside Promise.all() and have that be your return object.
Here's a full example. I added a try/catch block to handle promise rejections:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class resizeService {
constructor() {}
resizeImage(files: FileList, maxWidth: number, maxHeight: number) {
return Promise.all(
Array.from(files).map(file => {
return new Promise((res, rej) => {
try {
let image: HTMLImageElement = new Image();
image.src = URL.createObjectURL(file);
image.onload = () => res(this.handleOnLoad(image, maxWidth, maxHeight));
} catch (e) {
rej(e);
}
});
})
);
}
private handleOnLoad(
image: HTMLImageElement,
maxWidth: number,
maxHeight: number
) {
let width = image.width;
let height = image.height;
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let newHeight;
let newWidth;
const ratio = width / height;
// Calculate aspect ratio
if (width > height) {
newWidth = maxHeight * ratio;
newHeight = maxHeight;
} else {
newWidth = maxWidth * ratio;
newHeight = maxWidth;
}
canvas.width = newWidth;
canvas.height = newHeight;
// Draw image on canvas
ctx.drawImage(image, 0, 0, newWidth, newHeight);
return this.b64ToBlob(canvas.toDataURL('image/jpeg'));
}
/**
* Convert BASE64 to BLOB
* @param base64Image Pass Base64 image data to convert into the BLOB
*/
private b64ToBlob(base64Image: string) {
const parts = base64Image.split(';base64,');
const imageType = parts[0].split(':')[1];
const decodedData = window.atob(parts[1]);
const uInt8Array = new Uint8Array(decodedData.length);
for (let i = 0; i < decodedData.length; i) {
uInt8Array[i] = decodedData.charCodeAt(i);
}
return new Blob([uInt8Array], { type: imageType });
}
}
Now that resizeImage() is returning a Promise. You can invoke it inside an async/await function.
/** Component Class **/
public async resizeEvent(){
const newImages = await service.resizeImage(files, 1920, 1080);
}
