angular - 压缩图像后 reader.onload 不会重置
问题描述
我上传图片<input type="file" multiple (change)="selectFiles($event)">
然后我用这个显示图像列表:
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
if (typeof reader.result === 'string') {
this.files.push({
filename: file.name,
content: reader.result.split(',')[1]
} as ComplaintAttachmentInterface);
}
};
reader.onerror = (error) => {
console.log('Error: ', error);
};
和 :
<ng-container *ngIf="files.length">
<p-table [value]="files" styleClass="p-datatable-sm">
<ng-template pTemplate="body" let-file let-rowIndex="rowIndex">
<tr>
<td>{{file.filename}}</td>
<td class="text-center" style="width:50px;">
<i class="fas fa-trash cursor-pointer" (click)="removeFile(rowIndex)"></i>
</td>
</tr>
</ng-template>
</p-table>
</ng-container>
每次添加图像时,列表都会刷新而不会出现问题。
但是我想通过服务压缩图像,当我压缩图像时,列表不再刷新我必须单击以显示添加的图像,但是如果我从列表中创建控制台 console.log(this.files.length)
,它会告诉我列表包括元素,但 html 端的更新尚未完成。
如果有人知道压缩图像后重新激活列表的解决方案?
这是压缩服务:
压缩图像服务
import { Injectable } from '@angular/core' import { Observable } from 'rxjs' // in bytes, compress images larger than 1MB const fileSizeMax = 1 * 1024 * 1024 // in pixels, compress images have the width or height larger than 1024px const widthHeightMax = 1024 const defaultWidthHeightRatio = 1 const defaultQualityRatio = 0.7 @Injectable({ providedIn: 'root' }) export class CompressImageService { compress(file: File): Observable<File> { const imageType = file.type || 'image/jpeg' || 'image/png' const reader = new FileReader() reader.readAsDataURL(file) return Observable.create(observer => { // This event is triggered each time the reading operation is successfully completed. reader.onload = ev => { // Create an html image element const img = this.createImage(ev) // Choose the side (width or height) that longer than the other const imgWH = img.width > img.height ? img.width : img.height // Determines the ratios to compress the image let withHeightRatio = (imgWH > widthHeightMax) ? widthHeightMax/imgWH : defaultWidthHeightRatio let qualityRatio = (file.size > fileSizeMax) ? fileSizeMax/file.size : defaultQualityRatio // Fires immediately after the browser loads the object img.onload = () => { const elem = document.createElement('canvas') // resize width, height elem.width = img.width * withHeightRatio elem.height = img.height * withHeightRatio const ctx = <CanvasRenderingContext2D>elem.getContext('2d') ctx.drawImage(img, 0, 0, elem.width, elem.height) ctx.canvas.toBlob( // callback, called when blob created blob => { observer.next(new File( [blob], file.name, { type: imageType, lastModified: Date.now(), } )) }, imageType, qualityRatio, // reduce image quantity ) } } // Catch errors when reading file reader.onerror = error => observer.error(error) }) } private createImage(ev) { let imageContent = ev.target.result const img = new Image() img.src = imageContent return img } }
文件.component.ts
import { Component, Input } from '@angular/core';
import { FileAttachmentInterface } from '../../../../interfaces/file-
attachment.interface';
import { TranslateService } from '@ngx-translate/core';
import { CompressImageService } from '../../../../service/compress-image.service';
import { take } from 'rxjs/operators';
@Component({
selector: 'file-attachment',
templateUrl: './file-attachment.component.html',
})
export class FileAttachmentComponent {
@Input() public files: FileAttachmentInterface[] = [];
@Input() public maxSizeAttachment = 2097152;
private authorizedTypes: string[] = ['image/jpeg', 'image/png', 'application/pdf'];
public errors: string[] = [];
constructor(private _translateSrvc: TranslateService, private compressImage:
CompressImageService) {}
public selectFiles(event): void {
this.addFiles(event.target.files);
}
public removeFile(index: number) {
this.files.splice(index, 1);
}
private addFiles(files: FileList): void {
this.errors.length = 0;
Array.from(files).forEach((file) => {
if(file.type ==="image/jpeg" || file.type ==="image/png") {
console.log(`Image size before compressed: ${file.size} bytes.`)
this.compressImage.compress(file)
.pipe(take(1))
.subscribe(compressedImage => {
this.add(compressedImage);
})
} else {
this.add(file);
}
});
}
private add(file: File): void {
if (!this.authorizedTypes.includes(file.type)) {
this.errors.push(this._translateSrvc.instant('MODAL.FILE.ERROR_FILE_EXTENSION', {
file: file.name
}));
return;
}
if (file.size > this.maxSizeAttachment) {
this.errors.push(this._translateSrvc.instant('MODAL.FILE.ERROR_FILE_SIZE', {
file: file.name,
size: this.formatBytes(file.size)
}));
return;
}
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
if (typeof reader.result === 'string') {
if (this.files.length > 9) {
this.errors.push(this._translateSrvc.instant('MODAL.FILE.ERROR_MAX_FILES', {
file: file.name
}));
return;
}
this.files.push({
filename: file.name,
content: reader.result.split(',')[1]
} as FileAttachmentInterface);
console.log(this.files.length)
}
};
reader.onerror = (error) => {
console.log('Error: ', error);
};
}
private formatBytes = (numberInBytes, decimal = 2): string => {
if (0 === numberInBytes) {
return '0 Bytes';
}
const c = 0 > decimal ? 0 : decimal;
const d = Math.floor(Math.log(numberInBytes) / Math.log(1024));
return (
parseFloat((numberInBytes / Math.pow(1024, d)).toFixed(c)) +
' ' +
['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][d]
);
};
}
文件.component.html
<div class="d-none d-lg-flex">
<input type="file" multiple (change)="selectFiles($event)">
</div>
</div>
<ng-container *ngIf="files.length">
<p-table [value]="files" styleClass="p-datatable-sm">
<ng-template pTemplate="body" let-file let-rowIndex="rowIndex">
<tr>
<td>{{file.filename}}</td>
<td class="text-center" style="width:50px;">
<i class="fas fa-trash cursor-pointer" (click)="removeFile(rowIndex)"></i>
</td>
</tr>
</ng-template>
</p-table>
</ng-container>
解决方案
经过一番挖掘,我找到了一个解决方案,可以让我更新上传的图像列表。
如果我添加:this.ref.detectChanges();
在reader.onload()
此更新列表
文件.component.ts
import { ChangeDetectorRef, Component, Input } from '@angular/core';
import { FileAttachmentInterface } from '../../../../interfaces/file-
attachment.interface';
import { TranslateService } from '@ngx-translate/core';
import { CompressImageService } from '../../../../service/compress-image.service';
import { take } from 'rxjs/operators';
@Component({
selector: 'file-attachment',
templateUrl: './file-attachment.component.html',
})
export class FileAttachmentComponent {
@Input() public files: FileAttachmentInterface[] = [];
@Input() public maxSizeAttachment = 2097152;
private authorizedTypes: string[] = ['image/jpeg', 'image/png', 'application/pdf'];
public errors: string[] = [];
constructor(private _translateSrvc: TranslateService, private compressImage:
CompressImageService, private ref: ChangeDetectorRef) {}
public selectFiles(event): void {
this.addFiles(event.target.files);
}
public removeFile(index: number) {
this.files.splice(index, 1);
}
private addFiles(files: FileList): void {
this.errors.length = 0;
Array.from(files).forEach((file) => {
if(file.type ==="image/jpeg" || file.type ==="image/png") {
console.log(`Image size before compressed: ${file.size} bytes.`)
this.compressImage.compress(file)
.pipe(take(1))
.subscribe(compressedImage => {
this.add(compressedImage);
})
} else {
this.add(file);
}
});
}
private add(file: File): void {
if (!this.authorizedTypes.includes(file.type)) {
this.errors.push(this._translateSrvc.instant('MODAL.FILE.ERROR_FILE_EXTENSION', {
file: file.name
}));
return;
}
if (file.size > this.maxSizeAttachment) {
this.errors.push(this._translateSrvc.instant('MODAL.FILE.ERROR_FILE_SIZE', {
file: file.name,
size: this.formatBytes(file.size)
}));
return;
}
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
if (typeof reader.result === 'string') {
if (this.files.length > 9) {
this.errors.push(this._translateSrvc.instant('MODAL.FILE.ERROR_MAX_FILES', {
file: file.name
}));
return;
}
this.files.push({
filename: file.name,
content: reader.result.split(',')[1]
} as FileAttachmentInterface);
}
this.ref.detectChanges();
};
reader.onerror = (error) => {
console.log('Error: ', error);
};
}
private formatBytes = (numberInBytes, decimal = 2): string => {
if (0 === numberInBytes) {
return '0 Bytes';
}
const c = 0 > decimal ? 0 : decimal;
const d = Math.floor(Math.log(numberInBytes) / Math.log(1024));
return (
parseFloat((numberInBytes / Math.pow(1024, d)).toFixed(c)) +
' ' +
['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][d]
);
};
}