0

Consider a component of which the template include a file input. The input can be opened by calling a public open() method which triggers a click on the nativeElement of the input.

@Component({
  selector: 'child',
  template: '<input type="file" #input/>',
})
export class ChildComponent {
  @ViewChild('input')
  public input: ElementRef;

  public open(): void {
    console.log('OPENING THE FILE INPUT');
    this.input.nativeElement.click();
  }
}

This ChildComponent is called in a ParentCompenent, which calls ChildComponent.open() in multiple contexts, as defined below :

@Component({
  selector: 'parent',
  template: '<child></child>',
})
export class ParentComponent {
  @ViewChild(ChildComponent)
  public child: ChildComponent;

  constructor( private parentService: ParentService ) {
  }

  public openChildInput(): void {
    // This works.
    // Output:
    //  OPENING THE FILE INPUT
    //  The file input opens

    this.child.open();
  }

  public waitAndOpenChildInput(): void {
    parentService.wait()
      .subscribe( () => {
        // This does not work.
        // Output:
        //  "OPENING THE FILE INPUT"
        //  The file input DOES NOT open...
        this.child.open();
      })
  }
}

In both cases, calling the open() method seems to work (as the console.log does show); yet the file input seems unwilling to open itself when called from a subscription.

Any ideas why ? Thanks in advance...

Alexis Facques
  • 1,783
  • 11
  • 19

1 Answers1

3

For security reasons, it is not possible in modern browsers to programatically invoke the click event on a file input - UNLESS it happens during a user interaction. Otherwise, websites and ads could spam users with file dialoges without them interacting with the page and e.g. have them unknowingly upload a file.

The issue with using an Observable is that, if it is asynchronous (i.e. when using the async scheduler), you leave the user interaction context and the browser will not open the file dialog.

You can solve this only by making your Observable synchronous (which, actually, almost all preset Observables are). If you have e.g. a HTTP request, this won't be possible and you have to look for alternatives for opening the file dialog (though I'm not sure if any of those would work in your setup).

Here is an GitHub issue that is similiar to your problem with an explanation:

Therefore [due to the asynchronity], the click() call [...] is not triggered by a user-event, from Chrome's perspective, and consequently is ignored.

ggradnig
  • 13,119
  • 2
  • 37
  • 61
  • That I didn't know, thanks it was quite informative ! I guess I'll work around the app design in order for the file input call to be in the context of the clic event. Many thanks ! – Alexis Facques Nov 14 '18 at 20:35