ngx-dropzone icon indicating copy to clipboard operation
ngx-dropzone copied to clipboard

Handle blur event for updateOn blur with reactive form

Open pepew-le-boss opened this issue 4 years ago • 1 comments

I implement ngx-dropzone correctly and everything is working fine. I switched my form from updateOn: 'submit' to updateOn: 'blur'.

Even though it's working, the behaviour is not the one intended:

  1. When I click on the dropzone, the blur is triggered and thus my custom error asking for at least 1 image is displayed. At this point, the file explorer is open and I haven't chose any picture.
  2. When the picture is selected the error do not disappear, so I need to click outside of the dropzone.

The second point could be understandable but the first one is not normal (I think).

To make the dropzone work with the updateOn: 'blur', I did the following:

  • Add tabindex="0" on the ngx-dropzone
  • Add (blur)="onBlur()" on the ngx-dropzone
  • Get the Viewchild of the dropzone in the TS
  • When I select a picture call this: this.dropzone?.nativeElement.blur()

Unfortunately, the problem remain same. In fact it's worse because when I click outside of the dropzone, the error does not disappear.

My question is: Is there a way to handle correctly the blur event of the dropzone ? (when I click on it => the blur event is not triggered and when I select a picture => the blur event is triggered)

My HTML:

<ngx-dropzone #dropzone (change)="onSelect($event)" (blur)="onBlur()" accept="image/jpeg,image/jpg,image/png" tabindex="0">
    <ngx-dropzone-label  *ngIf="files.length===0">
        <app-svg name="cloud_upload"></app-svg>
        <label>{{ 'common.dragndrop' | transloco }}</label>
    </ngx-dropzone-label>
    <custom-preview ngProjectAs="ngx-dropzone-preview" *ngFor="let f of files" [file]="f" [removable]="true" (removed)="onRemove(f)">
    </custom-preview>
</ngx-dropzone>

My TS:

export class DropzoneComponent implements ControlValueAccessor {

  @ViewChild('dropzone', { read: ElementRef }) dropzone: ElementRef | undefined = undefined

  files: File[] = []
  imgResultBeforeCompress: string = ''
  imgResultAfterCompress: string = ''
  imageBase64: string | ArrayBuffer | null | undefined = undefined
  isFileSizeUnder4MB: boolean = false

  onChange: any = () => { }
  onTouch: any = () => { }
  touched = false
  disabled = false

  constructor(private compressImage: CompressImageService) {}

  onSelect(event: NgxDropzoneChangeEvent) {
    if (this.disabled) return
    // this.markAsTouched()
    event.addedFiles.forEach(file => {
      this.compressImage.compress(file, 0.5, (file: File) => {
        this.checkIfFileSizeIsUnder4MB(file)
        this.files.push(file)
      })
    })
    this.dropzone?.nativeElement.blur()
    this.onChange(this.files)
  }

  onRemove(event: File) {
    if (this.disabled) return
    // this.markAsTouched()
    this.files.splice(this.files.indexOf(event), 1)
    this.onChange(this.files)
  }

  checkIfFileSizeIsUnder4MB(file: File) {
    if (file.size > 4000000) {
      this.isFileSizeUnder4MB = false
    } else {
      this.isFileSizeUnder4MB = true
    }
  }

  /* Methods needed by ControlValueAccessor to transform this component into a "form friendly" component */

  registerOnChange(providedFunction: any) {
    this.onChange = providedFunction
  }

  registerOnTouched(providedFunction: any) {
    this.onTouch = providedFunction
  }

  writeValue(providedValue: any) {
    if (providedValue) {
      this.files = providedValue
      this.files.forEach(file => this.checkIfFileSizeIsUnder4MB(file))
    }
  }

  setDisabledState(providedDisabledVal: any) {
    this.disabled = providedDisabledVal
  }

  onBlur() {
    this.onTouch()
  }

  markAsTouched() {
    if (!this.touched) {
      this.onTouch()
      this.touched = true
    }
  }
  
  validate() {
    return !this.isFileSizeUnder4MB && {
      invalid: true,
    }
  }

}

pepew-le-boss avatar Dec 02 '21 00:12 pepew-le-boss

Hi @william-fargues,

thank you for your feedback on using the dropzone within a form. Unfortunately, the native <input type="file" /> element does not play very well together with Angular's form controls, and so does the dropzone.

The good news is that I am actually planning on improving the form control experience in the future by providing a wrapper directly with the component. Until then, maybe this article by Ben Nadel helps you.

Happy coding! 🤓

peterfreeman avatar Dec 19 '21 10:12 peterfreeman