角函数被调用两次并导致无限循环的FileReader

问题描述 投票:0回答:1

我正在编写一个简单的代码,以在一个Form中接受一些值,并在右侧显示表单值的列表。我已经创建了表单字段的模态类,并且在提交时,我正在将数据发送到服务。在其他组件中,我已订阅数据。由于某些未知的原因,第二个组件中的My函数被称为Twice。另外,我接受表单中的图像文件或图像URL,并生成在表单中以及列表中选择的文件的预览。要在Form中生成此文件的预览,它可以正常工作,但在Second组件中,相同的代码将进入无限循环。任何

我的主要表单组件html

<div class="row form">
  <div class="col-8">
    <form [formGroup]="songMetadata" (submit)="onSubmit(songMetadata)">
      <div class="row">
        <div class="col form-data">
          <mat-form-field>
            <mat-label>Song Name</mat-label>
            <input matInput formControlName="songName" />
          </mat-form-field>
          <br />
          <br />
          <mat-form-field>
            <mat-label>Artist Name</mat-label>
            <input matInput formControlName="artistName" />
          </mat-form-field>
          <br />
          <br />
          <mat-form-field>
            <mat-label>Album Name</mat-label>
            <input matInput formControlName="albumName" />
          </mat-form-field>
          <br />
          <br />
          <mat-form-field>
            <mat-label>Spotify URL</mat-label>
            <input matInput formControlName="url" />
          </mat-form-field>
          <br />
          <br />
          <mat-form-field>
            <mat-label>Other Description</mat-label>
            <textarea
              matInput
              formControlName="description"
              rows="4"
            ></textarea>
          </mat-form-field>
          <br />
          <br />
          <button
            type="submit"
            class="btn btn-primary"
            [disabled]="
              songMetadata.invalid ||
              (!isImageFileSelected && !isImageURLEntered)
            "
          >
            Submit
          </button>
        </div>
        <div class="col image-upload">
          <div
            dropZone
            class="text-center dropzone"
            (hovered)="changeIsHover($event)"
            (dropped)="fileDropped($event)"
            [class.hovering]="isHovering"
          >
            <img
              *ngIf="isImageFileSelected || isImageURLEntered"
              [src]="imagePreview"
              alt=""
              width="192"
              height="190"
            />
            <div
              class="drop-text"
              *ngIf="!isImageFileSelected && !isImageURLEntered"
            >
              Drag And Drop File Here
            </div>
          </div>
          <input
            type="text"
            placeHolder="Or Enter URL"
            (blur)="loadPreviewFromURL($event)"
          />
          <br />
          <label for="files" class="btn btn-primary">Or Select Image</label>
          <br />
          <input
            id="files"
            style="visibility:hidden;"
            type="file"
            (change)="fileSelected($event)"
          />
          <br />
          <!-- <input type="file" /> -->
        </div>
      </div>
    </form>
  </div>
  <!-- List View -->
  <div class="col-4">
    <list></list>
  </div>
</div>

主要组件TS。

import { DataService } from "./data.service";
import { AngularFireStorageModule } from "@angular/fire/storage";
import { Component, OnInit } from "@angular/core";
import { FormGroup, FormBuilder, Validators } from "@angular/forms";
import { Observable } from "rxjs";
import { ISong } from "./song";
import { Data } from "@angular/router";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
  songMetadata: FormGroup;
  selectedFile: File = null;
  isImageFileSelected: boolean;
  isImageURLEntered: boolean;
  isHovering: boolean;
  song: ISong;
  imagePreview: any;
  // // Upload Related Stuff
  // task: AngularFireUploadTask;
  // snapshot: Observable<any>;

  constructor(
    private formBuilder: FormBuilder,
    private service: DataService // private storage: AngularFireStorage
  ) {
    this.isImageFileSelected = false;
    this.isHovering = false;
    this.isImageURLEntered = false;
  }

  ngOnInit() {
    this.songMetadata = this.formBuilder.group({
      songName: ["xzcvzxv", Validators.required],
      artistName: ["xzcvcxzv", Validators.required],
      albumName: ["zxcvcxv", Validators.required],
      url: ["zxcvvc", Validators.required],
      description: ["zxcvzcxv", Validators.required]
    });
  }

  fileSelected(event: any) {
    this.isImageFileSelected = true;
    this.isImageURLEntered = false;
    this.selectedFile = event.target.files[0];
    this.loadPreview();
  }

  fileDropped(event: FileList) {
    this.isImageFileSelected = true;
    this.isImageURLEntered = false;
    this.selectedFile = event.item(0);
    this.loadPreview();
  }

  onSubmit(songForm: FormGroup) {
    event.preventDefault();
    if (this.isImageFileSelected) {
      this.song = {
        name: songForm.value.songName,
        artist: songForm.value.artistName,
        album: songForm.value.albumName,
        url: songForm.value.url,
        description: songForm.value.description,
        imageFile: this.selectedFile,
        imageURL: null
      };
    } else {
      this.song = {
        name: songForm.value.songName,
        artist: songForm.value.artistName,
        album: songForm.value.albumName,
        url: songForm.value.url,
        description: songForm.value.description,
        imageFile: null,
        imageURL: this.imagePreview
      };
    }
    this.service.addSong(this.song);
  }

  changeIsHover(isHovering: boolean) {
    this.isHovering = isHovering;
  }

  loadPreview = () => {
    let reader = new FileReader();
    reader.readAsDataURL(this.selectedFile);
    reader.onload = event => {
      this.imagePreview = reader.result;
    };
    console.log("PREVIEW HIT");
  };

  loadPreviewFromURL(event: any) {
    console.log(event.target.value);
    this.isImageURLEntered = true;
    this.isImageFileSelected = false;
    this.imagePreview = event.target.value;
  }
}

列表组件HTML

<div *ngFor="let s of songs; let i = index">
  <div class="col-4">
    <img
      *ngIf="s?.imageURL"
      [src]="livePrvw(s?.imageURL)"
      width="100"
      height="100"
      alt="Image URL Preview"
    />
    <img
      *ngIf="s?.imageFile"
      [src]="livePreview(s?.imageFile)"
      width="100"
      height="100"
      alt="Image FILE Preview"
    />
  </div>
  <div class="col-8">
    {{ s.name }}
  </div>
</div>

listComponent TS

import { DataService } from "./../data.service";
import { ISong } from "./../song";
import { Component, OnInit, OnChanges } from "@angular/core";

@Component({
  selector: "list",
  templateUrl: "./list.component.html",
  styleUrls: ["./list.component.scss"]
})
export class ListComponent implements OnInit {
  songs: ISong[];
  // song: ISong;

  constructor(private service: DataService) {}

  ngOnInit() {
    this.service.songsAsObservable.subscribe(data => {
      this.songs = data;
    });
  }

  //  also getting called twice
  livePreview(file: File) {
    // Going into infinite loop

    // let newReader = new FileReader();
    // let imagePreview: any;
    // newReader.readAsDataURL(file);

    // newReader.onload = event => {
    //   imagePreview = newReader.result;
    // };

    console.log(file);
  }

  // getting called twice somehow
  livePrvw(imageURL: string) {
    console.log(imageURL);
    return imageURL;
  }
}

要反复重申ListComponent中的方法两次,并且在Main Component中工作的FileReader代码在ListComponent中不起作用(进入无限循环)

StackBlitzhttps://stackblitz.com/github/Luciferdev7796/naada/tree/master

非常感谢任何帮助

angular typescript angular-services angular-file-upload
1个回答
0
投票

您的问题

您具有一个允许用户上载图像或指定图像URL的表单。

提交表单后,您将渲染上载的图像。

返回图像URL的函数多次运行或不起作用。

我的诊断

您当前的解决方案有一些问题。

  1. 多功能执行

您正在将<img src />绑定到解析URL的函数。每次运行更改检测时,都会查询您的功能。您应该在变更检测周期中尽可能少地进行处理。

通常,通过将组件changeDetectionStrategy设置为OnPush,注入ChangeDetectorRef并在您要触发更改检测时手动调用detectChanges(),可以解决此问题。但是,在这种情况下,我认为这可以解决裂缝,因此不是我建议的解决方案。

  1. 无法解析上传文件的URL

您正在尝试将<img src />绑定到通过FileReader获取数据URL的函数。文件读取器异步执行,因此您需要将其包装在某种Promise / Observable中,然后使用async管道进行调用。

但是出于第1点所述的原因,您不应该这样做。

我的方法

我将着重解决如何解析图像URL的特定问题,因为您的表单很复杂,因此超出了此问题的范围。

我在这里的主要重构是使URL解析脱离更改检测周期。我先解析URL,然后使用解析的URL将项目添加到数组。

我的实现

我将把您的问题抽象到一个简单的应用程序中,您可以在其中上传图像和/或指定图像URL。提交表单后,将显示已解析的图像。

该组件相当琐碎,因此我仅在此处显示该服务:

image.service.ts

export class ImageService {
  private images$ = new BehaviorSubject<{ url: string, type: string }[]>([]);
  private images: { url: string, type: string }[] = [];

  addFromFile(file: File) {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = event => {
      this.addImage({
        url: reader.result.toString(),
        type: 'fromFile'
      });
    };    
  }

  addFromUrl(url: string) {
    this.addImage({
      url: url,
      type: 'fromUrl'
    });
  }

  getImages(): Observable<{ url: string, type: string }[]> {
    return this.images$.asObservable();
  }

  private addImage(image: { url: string, type: string }) {
    this.images.push(image);
    this.images$.next(this.images);
  }
}

注意,解析URL后,如何仅将图像推送到数组和主题中。

与您的表格相比,我绝对简化了很多,但是我认为您可以从这种方法中得到一些启发来解决您的问题。

DEMO:https://stackblitz.com/edit/angular-yhnnwv

© www.soinside.com 2019 - 2024. All rights reserved.