47

I'm trying to trigger the click event of the <input type="file"> element from the button.

<input id="upload"
    type="file"
    ng-file-select="onFileSelect($files)"
    style="display: none;">

<button type="button"
    ng-click="angular.element('#upload').trigger('click');">Upload</button>

It's common practice to hide the uglified beast known as <input type=file> and trigger it's click event by some other means.

Pratik Gadoya
  • 1,420
  • 1
  • 16
  • 27
chovy
  • 72,281
  • 52
  • 227
  • 295

13 Answers13

35

If your input and button are siblings (and they are in your case OP):

<input id="upload"
    type="file"
    ng-file-select="onFileSelect($files)"
    style="display: none;">

<button type="button" uploadfile>Upload</button>

Use a directive to bind the click of your button to the file input like so:

app.directive('uploadfile', function () {
    return {
      restrict: 'A',
      link: function(scope, element) {

        element.bind('click', function(e) {
            angular.element(e.target).siblings('#upload').trigger('click');
        });
      }
    };
});
J Savage Uphoff
  • 366
  • 4
  • 5
  • 3
    Great directive - it becomes a lot more reusable if you pass in the sibling id in a scope parameters `scope: { siblingId: '=' }` the use the directive like `` – Amicable Sep 30 '15 at 11:18
  • Works perfectly--anyone care to explain why triggering a click from a directive like above works, while doing the same from a controller causes an "$apply is already in progress" error? – cjones26 Oct 22 '15 at 17:34
  • 1
    @slashp you may need to wrap your call in a `$timeout` so you make sure it runs within an `$apply` cycle – yorch Oct 24 '15 at 08:52
  • this works for me using angularjs1.4.5 and take note to include jquery to use .siblings(), but I still have a problem. I have a fab md-button with icon inside. It works only if I click within the md-button but outside the icon, so I ended up tweaking the directive – Alex Coroza Mar 10 '16 at 06:10
  • 1
    You may want to do this way: angular.element(document.querySelector('#myId')).triggerHandler('click'); – bob Nov 29 '16 at 23:20
15

So it was a simple fix. Just had to move the ng-click to a scope click handler:

<input id="upload"
    type="file"
    ng-file-select="onFileSelect($files)"
    style="display: none;">

<button type="button"
    ng-click="clickUpload()">Upload</button>



$scope.clickUpload = function(){
    angular.element('#upload').trigger('click');
};
chovy
  • 72,281
  • 52
  • 227
  • 295
6

I had this same issue and this fiddle is the shizzle :) It uses a directive to properly style the file field and you can even make it an image or whatever.

http://jsfiddle.net/stereosteve/v5Rdc/7/

/*globals angular:true*/
var buttonApp = angular.module('buttonApp', [])

buttonApp.directive('fileButton', function() {
  return {
    link: function(scope, element, attributes) {

      var el = angular.element(element)
      var button = el.children()[0]

      el.css({
        position: 'relative',
        overflow: 'hidden',
        width: button.offsetWidth,
        height: button.offsetHeight
      })

      var fileInput = angular.element('<input type="file" multiple />')
      fileInput.css({
        position: 'absolute',
        top: 0,
        left: 0,
        'z-index': '2',
        width: '100%',
        height: '100%',
        opacity: '0',
        cursor: 'pointer'
      })

      el.append(fileInput)


    }
  }
})
<div ng-app="buttonApp">

  <div file-button>
    <button class='btn btn-success btn-large'>Select your awesome file</button>
  </div>

  <div file-button>
    <img src='https://www.google.com/images/srpr/logo3w.png' />
  </div>

</div>
Amicable
  • 3,115
  • 3
  • 49
  • 77
Osiloke
  • 239
  • 2
  • 2
  • This answer fits the spirit of angular js. Should be the accepted answer. I'm however still search on github for a offical project with such a directive. – Gordon Sun Mar 21 '15 at 07:42
  • Triggering a click on the file input has proved impossible, so this method of overlaying a transparent file input in order to capture the click is the only working solution I've found. – wayfarer_boy Jun 30 '15 at 22:12
  • @Osiloke I would like to add a directive to the input. Considering $compile, how should I compile and append? – scuba88 Oct 05 '15 at 17:18
3

If you are getting $scope binding errors make sure you wrap the click event code on a setTimeout Function.

VIEW

<input id="upload"
    type="file"
    ng-file-select="onFileSelect($files)"
    style="display: none;">

<button type="button"
    ng-click="clickUpload()">Upload</button>

CONTROLLER

$scope.clickUpload = function(){
    setTimeout(function () {
      angular.element('#upload').trigger('click');
    }, 0);
};
Rick
  • 12,606
  • 2
  • 43
  • 41
2

for jqLite just use triggerHandler with event name, To simulate a "click" try:

angular.element("tr").triggerHandler("click");

Here's the list of jQLite commands

Kieran Ryan
  • 593
  • 2
  • 8
  • 18
  • Even this code snippet produces same error. Error: [jqLite:nosel] Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element http://errors.angularjs.org/1.5.3/jqLite/nosel – Dileep Mettu Apr 19 '17 at 08:38
1

Simply have them in the same controller, and do something like this:

HTML:

<input id="upload"
    type="file"
    ng-file-select="onFileSelect($files)"
    style="display: none;">

<button type="button"
    ng-click="startUpload()">Upload</button>

JS:

var MyCtrl = [ '$scope', '$upload', function($scope, $upload) {
  $scope.files = [];
  $scope.startUpload = function(){
    for (var i = 0; i < $scope.files.length; i++) {
      $upload($scope.files[i]);
    } 
  }
  $scope.onFileSelect = function($files) {
     $scope.files = $files;
  };
}];

This is, in my opinion, the best way to do it in angular. Using jQuery to find the element and trigger an event isn't the best practice.

Mosho
  • 7,099
  • 3
  • 34
  • 51
  • 2
    Your missing the point, the `` button is hidden because its a PITA to style properly across browsers. – chovy Jul 08 '14 at 16:23
1

I just came across this problem and have written a solution for those of you who are using Angular. You can write a custom directive composed of a container, a button, and an input element with type file. With CSS you then place the input over the custom button but with opacity 0. You set the containers height and width to exactly the offset width and height of the button and the input's height and width to 100% of the container.

the directive

angular.module('myCoolApp')
  .directive('fileButton', function () {
    return {
      templateUrl: 'components/directives/fileButton/fileButton.html',
      restrict: 'E',
      link: function (scope, element, attributes) {

        var container = angular.element('.file-upload-container');
        var button = angular.element('.file-upload-button');

        container.css({
            position: 'relative',
            overflow: 'hidden',
            width: button.offsetWidth,
            height: button.offsetHeight
        })

      }

    };
  });

a jade template if you are using jade

div(class="file-upload-container") 
    button(class="file-upload-button") +
    input#file-upload(class="file-upload-input", type='file', onchange="doSomethingWhenFileIsSelected()")  

the same template in html if you are using html

<div class="file-upload-container">
   <button class="file-upload-button"></button>
   <input class="file-upload-input" id="file-upload" type="file" onchange="doSomethingWhenFileIsSelected()" /> 
</div>

the css

.file-upload-button {
    margin-top: 40px;
    padding: 30px;
    border: 1px solid black;
    height: 100px;
    width: 100px;
    background: transparent;
    font-size: 66px;
    padding-top: 0px;
    border-radius: 5px;
    border: 2px solid rgb(255, 228, 0); 
    color: rgb(255, 228, 0);
}
.file-upload-input {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
Benjamin Conant
  • 1,684
  • 1
  • 14
  • 17
0

The solution, as pointed out by other answers, is to use

angular.element(element).trigger(event);

Here's an example of how I randomly select multiple select elements:

$scope.randomize = function(){
    var games = [].slice.call(document.querySelectorAll('.games select'));

    games.forEach(function(e){
        // Logically change the element (Angular won't know about this)
        e.selectedIndex = parseInt(Math.random() * 100, 10) < 50 ? 1 : 2;

        // Manually tell Angular that the DOM has changed
        angular.element(e).trigger('change');
    });
};
rodrigo-silveira
  • 12,607
  • 11
  • 69
  • 123
0

I took the answer posted by Osiloke (Which was the easiest and most complete imho) and I added a change event listener. Works great! Thanks Osiloke. See below if you are interested:

HTML:

  <div file-button>
        <button class='btn btn-success btn-large'>Select your awesome file</button>
   </div>

Directive:

app.directive('fileButton', function() {
  return {
    link: function(scope, element, attributes) {

      var el = angular.element(element)
      var button = el.children()[0]

      el.css({
        position: 'relative',
        overflow: 'hidden',
        width: button.offsetWidth,
        height: button.offsetHeight
      })

      var fileInput = angular.element('<input id='+scope.file_button_id+' type="file" multiple />')
      fileInput.css({
        position: 'absolute',
        top: 0,
        left: 0,
        'z-index': '2',
        width: '100%',
        height: '100%',
        opacity: '0',
        cursor: 'pointer'
      })

      el.append(fileInput)
      document.getElementById(scope.file_button_id).addEventListener('change', scope.file_button_open, false); 
    }
  }
});

Controller:

$scope.file_button_id = "wo_files";    
$scope.file_button_open = function() 
{
  alert("Files are ready!");
}
Tony
  • 23
  • 1
  • 4
0

One more directive

html

<btn-file-selector/>

code

.directive('btnFileSelector',[function(){
  return {
    restrict: 'AE', 
    template: '<div></div>', 
    link: function(s,e,a){

      var el = angular.element(e);
      var button = angular.element('<button type="button" class="btn btn-default btn-upload">Add File</button>'); 
      var fileForm = angular.element('<input type="file" style="display:none;"/>'); 

      fileForm.on('change', function(){
         // Actions after the file is selected
         console.log( fileForm[0].files[0].name );
      });

      button.bind('click',function(){
        fileForm.click();
      });     


     el.append(fileForm);
     el.append(button);
    }
  }  
}]); 
0

best and simple way to use native java Script which is one liner code.

document.querySelector('#id').click();

Just add 'id' to your html element like

<button id="myId1" ng-click="someFunction()"></button>

check condition in javascript code

if(condition) { document.querySelector('#myId1').click(); }

Hunter
  • 3,080
  • 20
  • 23
-1
<input type="file" #imageFile />

<button (click)="imageFile.click()"></button>
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
  • Welcome to Stack Overflow. Please consider including a brief explanation of [how and why this solves the problem](https://meta.stackoverflow.com/q/392712/13138364). This will help future readers to better understand your solution. - [From Review](https://stackoverflow.com/review/low-quality-posts/30221598) – tdy Oct 31 '21 at 08:59
  • The answer caters to an Angular implementation. The question was around AngularJS. – Ritwik Sen Oct 31 '21 at 11:38
-2

I think you are over complicated things a bit. Do you really need to trigger a click on the input from your button ?

I suggest you just apply a proper style to your input and the ngFileSelect directive will do the rest and call your onFileSelect function whenever a file is submitted :

input.file {
    cursor: pointer;
    direction: ltr;
    font-size: 23px;
    margin: 0;
    opacity: 0;
    position: absolute;
    right: 0;
    top: 0;
    transform: translate(-300px, 0px) scale(4);
}
Alexandre Nucera
  • 2,183
  • 2
  • 21
  • 34
  • styling input file buttons is not a good solution, since they very completely between browsers. Its much easier (and is common practice) to just hide the ugly beast and trigger its click event programmatically instead. – chovy Jul 08 '14 at 16:22