Невозможно установить глобальную переменную внутри метода onload

#javascript #angular

#javascript #угловой

Вопрос:

Я создаю приложение Angular 9. В этом приложении я получил компонент загрузки изображений.

Чтобы убедиться, что загружены правильные вещи, я хочу проверить размеры выбранных изображений. Я делаю это с помощью приведенного ниже кода (который работает).

 let img = new Image();
          img.src = window.URL.createObjectURL(fileList[index]);
          img.onload = () => {
            if (img.width < this.allowedWidth amp;amp; img.height < this.allowedHeight) {
              this.uploadValid = true;
              this.isDoneUploading = false;
            } else {
              this.uploadValid = false;
              this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_dimenstions');
            }
          }
 

Я могу получить доступ к переменным this.allowedWidth и this.allowedHeight, которые я установил в верхней части страницы компонента, но, как ни странно, я не могу установить переменные this.uploadValid и this.uploadErrorMessage .

Или, по крайней мере, я не могу установить их в «глобальном» контексте компонента только в операторе If / else .

Я тоже попробовал следующий код, но это не помогло.

 let img = new Image();
let that = this;
              img.src = window.URL.createObjectURL(fileList[index]);
              img.onload = () => {
                if (img.width < that.allowedWidth amp;amp; img.height < that.allowedHeight) {
                  that.uploadValid = true;
                  that.isDoneUploading = false;
                } else {
                  that.uploadValid = false;
                  that.uploadErrorMessage = that.getTranslation('files.forms.labels.error_dimenstions');
                }
              }
 

Обновить

Это весь метод, в котором находится этот метод. Он вызывается при выборе файла.

 handleDrop(fileList: FileList) {
    this.files = [];
    this.isDropzoneActive = false;
    this.uploadingFileCount  = fileList.length
    for (let index = 0; index < fileList.length; index  ) {
      if (this.allowedTypes.includes(fileList[index].type)) {
        if (fileList[index].size < this.allowedSize) {
          let img = new Image();
          img.src = window.URL.createObjectURL(fileList[index]);
          img.onload = () => {
            if (img.width < this.allowedWidth amp;amp; img.height < this.allowedHeight) {
              this.uploadValid = true;
              this.isDoneUploading = false;
            } else {
              this.uploadValid = false;
              this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_dimenstions');
            }
          }
        } else {
          this.uploadValid = false;
          this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_size');
        }
      } else {
        this.uploadValid = false;
        this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_format');
      }
      const item = {
        id: null,
        file_name: fileList[index].name,
        file_type: fileList[index].type,
        file_size: fileList[index].size,
        file_url: null
      }
      this.uploadItems.push(item);
      const file = {
        id: Math.random().toString(36).substr(2, 15),
        file: fileList[index],
        uploaded: false,
        valid: this.uploadValid,
        error: this.uploadErrorMessage
      }
      console.log(file);
      this.uploadQueue.push(file);
    }
    // this.submitUploadForm();
  }
 

Комментарии:

1. Пожалуйста, покажите, как это вложенный код и как он вызывается?

2. Пожалуйста, добавьте больше кода. Здесь сложно получить контекст кода, которым вы поделились, структуру компонентов и т. Д. Обычно импорт изображений выполняется в директивах, а не в компонентах, и включается в форму. Вы также должны использовать FormControl вместо своих собственных переменных.

3. Я обновил вопрос с помощью всего метода. Спасибо! @Jean-XavierRaynaud

4. Это долгий путь, но попробуйте сделать обработчик событий функцией со стрелкой: handleDrop = (fileList: FileList) => {...}

5. К сожалению, это тоже не сработало. Очень странно, что я могу получить доступ к значениям this.allowedWidth, но я не могу установить значения (по крайней мере, не так, чтобы они изменялись глобально).

Ответ №1:

Используя директиву и FormControl для решения аналогичной проблемы, мой код выглядит следующим образом:

директива:

 // angular imports
import { Directive, HostBinding, HostListener, Output, EventEmitter } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';

// models imports
import { FileHandle } from '../models';


@Directive({
 selector: '[appFilesDrop]'
})
export class FilesDropDirective {
  @Output() files: EventEmitter<FileHandle[]> = new EventEmitter();

  @HostBinding('style.background') private background = '#eee';

  constructor(
    private sanitizer: DomSanitizer
  ) { }

  @HostListener('dragover', ['$event']) public onDragOver(evt: DragEvent) {
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#999';
  }

  @HostListener('dragleave', ['$event']) public onDragLeave(evt: DragEvent) {
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#eee';
  }

  @HostListener('drop', ['$event']) public onDrop(evt: DragEvent) {
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#eee';

    const files: FileHandle[] = [];
    // tslint:disable-next-line: prefer-for-of
    for (let i = 0; i < evt.dataTransfer.files.length; i  ) {
      const file = evt.dataTransfer.files[i];
      const url = this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(file));
      files.push({ file, url });
    }
    if (files.length > 0) {
      this.files.emit(files);
    }
  }
}
 

модели.ts:

 export interface FileHandle {
  url: SafeUrl;
  file?: File;
}
 

html — код:

 <!-- Image upload field -->
        <ng-container *ngIf="formField.type === 'filesUpload'" class="example-full-width image-field-container">
          <mat-form-field
            appearance="outline"
            floatLabel="always"
            class=".example-full-width"
          >
              <mat-label>{{ objectName }} {{formField.label}}</mat-label>
              <!-- This block is only displayed if there's no image -->
              <!-- it's necessary because we need a formControlName in a mat-form-field -->
              <input
                *ngIf="formGroup.controls[formField.key].value === null"
                class="image-input"
                matInput
                [formControlName]="formField.key"
                type="text"
                appFilesDrop
                [placeholder]="formField.placeholder"
                autocomplete="off"
                (files)="filesDropped($event, formField.key)"
                readonly
              >
              <!-- Display errors if any -->
              <mat-error
                *ngIf="formGroup.controls[formField.key].hasError('required') amp;amp; (formGroup.controls[formField.key].dirty || formGroup.controls[formField.key].touched)"
              >
                A {{formField.label.toLocaleLowerCase()}} is required
              </mat-error>
              <mat-error     *ngIf="formGroup.controls[formField.key].hasError('tooLongArray')">
                You must drop only one image, you dropped {{formGroup.controls[formField.key].errors.tooLongArray.length}} images.
              </mat-error>
              <mat-error     *ngIf="formGroup.controls[formField.key].hasError('forbiddenMime')">
                You must drop files of type {{formGroup.controls[formField.key].errors.forbiddenMime.forbidden.join(', ')}}.
                All your files must be of type {{formGroup.controls[formField.key].errors.forbiddenMime.allowed.join(', ')}}
              </mat-error>
              <mat-error     *ngIf="formGroup.controls[formField.key].hasError('tooLargeFile')">
                The file(s) {{formGroup.controls[formField.key].errors.tooLargeFile.forbidden.join(', ')}} is/are too big.
                The maximum allowed size is     {{formGroup.controls[formField.key].errors.tooLargeFile.allowed/1024 | number}}kb
              </mat-error>
              <!-- If we have an image we need to display it. -->
              <!-- We created a hidden mat-form-field and display the image -->
              <ng-container *ngIf="formGroup.controls[formField.key].value !== null">
                <div class="">
                  <input matInput [formControlName]="formField.key" style="visibility: hidden;" class="invisible">
                  <img [src]="formGroup.controls[formField.key].value[0].url" class="image-uploaded">
                </div>
                <button mat-mini-fab color="warn" (click)="removeFiles(formField.key)"><mat-icon>delete</mat-icon></button>
              </ng-container>
          </mat-form-field>
        </ng-container>
 

Общая логика проверки немного сложна, потому что я использую сервис, который генерирует FormGroup динамически, но логику легко понять, даже если вы не можете скопировать / вставить ее:

 // file fields
        case('filesUpload'): {
          const options = field.options as FileFieldOptions;

          // build the validators
          validators.push(mimeTypeValidator(options.allowedMimes));
          if (!options.multiple) { validators.push(arrayLengthValidator(1)); }
          if (options.maxSize) { validators.push(fileSizeValidator(options.maxSize)); }

          // get the initial value
          if (!options.multiple amp;amp; initialValue amp;amp; initialValue[field.key]) {
            // we only have 1 object, so we will have on url from the api and we can provide the formControl as a 1 object control
            initialFieldValue = [{
              url: initialValue[field.key]
            }] as Array<FileHandle>;
          }
          break;
 

Забыл добавить пример валидаторов:

 For the file size validator:
    // angular imports
import { ValidatorFn, AbstractControl, } from '@angular/forms';

// model imports
import { FileHandle } from '../models';

export function fileSizeValidator(maxFileSize: number): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    // validate when there's no value
    if (control.value === null || control.value === undefined ) { return     null; }
    // validate when we only have a url (it comes from server)
    if (control.value[0].file === undefined) { return null; }
    const fileHandles = control.value as Array<FileHandle>;
    const tooLargeFiles = fileHandles.filter(fileHandle => fileHandle.file.size > maxFileSize).map(fileHandle => fileHandle.file.name);
    return tooLargeFiles.length > 0 ? {tooLargeFile: {forbidden: tooLargeFiles, allowed: maxFileSize}} : null;
  };
}
 

и валидатор MimeType:

 // angular imports
import { ValidatorFn, AbstractControl, } from '@angular/forms';

// model imports
import { FileHandle } from '../models';

export function mimeTypeValidator(allowedMimeTypes: Array<string>): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    // validate when there's no value
    if (control.value === null || control.value === undefined ) { return null; }
    // validate when we only have a url (it comes from server)
    if (control.value[0].file === undefined) { return null; }
    const fileHandles = control.value as Array<FileHandle>;
    const forbiddenMimes = fileHandles.map(fileHandle => fileHandle.file.type).filter(mime => !allowedMimeTypes.includes(mime));
    return forbiddenMimes.length > 0 ? {forbiddenMime: {forbidden: forbiddenMimes, allowed: allowedMimeTypes}} : null;
  };
}