簡體   English   中英

Angular 8 - 在前端更改檢測問題上調整圖像大小

[英]Angular 8 - Resizing image on the front-end change-detection issue

我正在嘗試從我在網上找到的幾個教程構建一個圖像壓縮器服務。 服務本身按預期工作,它接收一個圖像作為 File ,然后壓縮它並返回一個Observable 一切都很好,只是我想在我的組件中使用壓縮圖像,然后再將它上傳到服務器。

該組件不會檢測新的壓縮圖像何時通過異步管道到達。 如果我手動訂閱 Observable ,我會按預期獲得圖像數據,但是如果我嘗試用它更新組件屬性,它不會立即更改視圖,而是使用舊的“圖像數據”更改它,如果我嘗試壓縮一個新的 Image 。

我發現如果部分代碼在 ngZone 之外解決,則可能會出現此問題,因此我找到了一種解決方法(請參閱下面的代碼),即注入ApplicationRef並使用.tick() ,這實際上效果很好,但使我的服務幾乎無法重用。

我的問題是:服務代碼的哪一部分在 ngZone 之外運行以及可能的修復或解決方法是什么,以便服務可以在其他組件中重用,而不必在每次服務發出數據時注入 ApplicationRef 和 .tick() 。

這是我的服務代碼

 import { Observable ,  Subscriber } from 'rxjs';
import { Injectable } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';

@Injectable({
  providedIn: 'root'
})

export class ImageCompressorService {

// globals
private _currentFile : File ;
private _currentImage : ICompressedImage = {} ;

// Constructor
constructor( private sanitizer : DomSanitizer) {}

// FileReader Onload callback
readerOnload(observer : Subscriber<ICompressedImage>)  {
 return (progressEvent : ProgressEvent) => {
  const img = new Image();
  img.src = (progressEvent.target as any).result;
  img.onload = this.imageOnload(img , observer);
}
}

// Image Onload callback
 imageOnload(image : HTMLImageElement , observer : Subscriber<ICompressedImage>) {
  return () => {
  const canvas = document.createElement('canvas');
  canvas.width = 100;
  canvas.height = 100;
  const context = <CanvasRenderingContext2D>canvas.getContext('2d');
  context.drawImage(image , 0 , 0 , 100 , 100);
  this.toICompressedImage(context , observer);
}}

// Emit CompressedImage
toICompressedImage(context : CanvasRenderingContext2D , observer : Subscriber<ICompressedImage> ) {
  context.canvas.toBlob(
    (blob) => {
      this._currentImage.blob = blob ;
      this._currentImage.image = new File([blob] , this._currentFile.name , {type : 'image/jpeg', lastModified : Date.now()} );
      this._currentImage.imgUrl = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob));
      this._currentImage.name = this._currentFile.name ;
      observer.next(this._currentImage);
      observer.complete();
    } ,
    'image/jpeg' ,
    1
  );
}

//  Compress function
 compress(file : File) : Observable<ICompressedImage> {
   this._currentFile = file ;
   return new Observable<ICompressedImage>(
     observer => {
       const currentFile = file;
       const reader = new FileReader();
       reader.readAsDataURL(currentFile);
       reader.onload = this.readerOnload(observer);
     }
   );
 }
}

// Image Data Interface
export interface ICompressedImage {
  name? : string;
  image? : File ;
  blob? : Blob ;
  imgUrl? : SafeUrl ;
}

這是我的component.ts

import { Component, OnInit, ApplicationRef } from '@angular/core';
import { ImageCompressorService, ICompressedImage } from 'src/app/shared/services/image-compressor.service';


@Component({
  selector: 'app-new-project',
  templateUrl: './new-project.component.html',
  styleUrls: ['./new-project.component.css']
})
export class NewProjectComponent implements OnInit  {
// globals
private selectedImage ;
compressedImage :  ICompressedImage = {name : 'No file selected'};


// Constructor
  constructor( private compressor : ImageCompressorService,
               private ar : ApplicationRef
             ) {}
// OnInit implementation
     ngOnInit(): void {}

// Compress method
  compress(fl : FileList) {
if (fl.length>0) {
    this.selectedImage = fl.item(0);
    this.compressor
    .compress(this.selectedImage)
    .subscribe(data => {
     this.compressedImage = data ;
     this.ar.tick();
    });
  } else {
    console.error('No file/s selected');

  }
  }


}

這是我的組件HTML 模板

<div style='border : 1px solid green;'>
    <input type='file' #SelectedFile (change)="compress($event.target.files)" accept='image/*' >
</div>


<div
style = 'border : 1px solid blue ; height : 200px;'
*ngIf="compressedImage " >
 <strong>File Name : </strong>{{ compressedImage?.name }}

<img *ngIf="compressedImage?.imgUrl as src"
[src]= 'src' >
</div>

我展示我的代碼的方式,它工作得很好。 嘗試注釋掉this.ar.tick(); component.ts文件的Compress Method中並查看更改。

經過幾個小時的挖掘,我找到了一個可行的解決方案。 我在我的服務中注入了 NgZone 包裝器。 之后,在我的壓縮方法中,我使用 zone.runOutsideAngular() 運行所有文件處理代碼,從而故意阻止 ChangeDetection,一旦調整大小操作完成並且新的壓縮圖像可用,我將運行下一個方法觀察者(訂閱者)與 zone.Run() ,它實際上在 Angular 的 zone 內運行代碼,強制 ChangeDetection 。 我已經測試了在我的組件中手動訂閱生成的 observable 以及通過異步管道訂閱。 兩者都像魅力一樣工作。 使用異步管道發布代碼。

服務.ts :

import { Observable ,  Subscriber } from 'rxjs';
import { Injectable, NgZone } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';

@Injectable({
  providedIn: 'root'
})

export class ImageCompressorService {

// globals
private _currentFile : File ;
private _currentImage : ICompressedImage = {} ;

// Constructor
constructor( private sanitizer : DomSanitizer , private _zone : NgZone) {

}

// FileReader Onload callback
readerOnload(observer : Subscriber<ICompressedImage>)  {
 return (progressEvent : ProgressEvent) => {
  const img = new Image();
  img.src = (progressEvent.target as any).result;
  img.onload = this.imageOnload(img , observer);
}
}

// Image Onload callback
 imageOnload(image : HTMLImageElement , observer : Subscriber<ICompressedImage>) {
  return () => {
  const canvas = document.createElement('canvas');
  canvas.width = 100;
  canvas.height = 100;
  const context = <CanvasRenderingContext2D>canvas.getContext('2d');
  context.drawImage(image , 0 , 0 , 100 , 100);
  this.toICompressedImage(context , observer);
}}

// Emit CompressedImage
toICompressedImage(context : CanvasRenderingContext2D , observer : Subscriber<ICompressedImage> ) {
  context.canvas.toBlob(
    (blob) => {
      this._currentImage.blob = blob ;
      this._currentImage.image = new File([blob] , this._currentFile.name , {type : 'image/jpeg', lastModified : Date.now()} );
      this._currentImage.imgUrl = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob));
      this._currentImage.name = this._currentFile.name ;
      this._zone.run(() => {
        observer.next(this._currentImage);
        observer.complete();
      })

    } ,
    'image/jpeg' ,
    1
  );
}

//  Compress function
 compress(file : File) : Observable<ICompressedImage> {
   this._currentFile = file ;
   return new Observable<ICompressedImage>(
     observer => {
       this._zone.runOutsideAngular(() => {
        const currentFile = file;

        const reader = new FileReader();
        reader.readAsDataURL(currentFile);
        reader.onload = this.readerOnload(observer);
       })

     }
   );
 }
}

// Image Data Interface
export interface ICompressedImage {
  name? : string;
  image? : File ;
  blob? : Blob ;
  imgUrl? : SafeUrl ;
}

組件.ts:

import { Component, OnInit } from '@angular/core';
import { ImageCompressorService, ICompressedImage } from 'src/app/shared/services/image-compressor.service';
import { Observable } from 'rxjs';


@Component({
  selector: 'app-new-project',
  templateUrl: './new-project.component.html',
  styleUrls: ['./new-project.component.css']
})
export class NewProjectComponent implements OnInit  {
// globals
private selectedImage ;
compressedImage : Observable<ICompressedImage>;

// Constructor
  constructor( private compressor : ImageCompressorService) {}

// OnInit implementation
     ngOnInit(): void {}

// Compress method
  compress(fl : FileList) {
if (fl.length>0) {
    this.selectedImage = fl.item(0);
  this.compressedImage =  this.compressor.compress(this.selectedImage)
  } else {

    console.error('No file/s selected');

  }
  }
}

組件.html:

<div style='border : 1px solid green;'>
    <input type='file' #SelectedFile (change)="compress($event.target.files)" accept='image/*' >
</div>


<div
style = 'border : 1px solid blue ; height : 200px;'
*ngIf="compressedImage | async as image" >
 <strong>File Name : </strong>{{ image.name }}

<img *ngIf="image.imgUrl as src"
[src]= 'src' >
</div>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM