AngularJs: Как проверить наличие изменений в полях ввода файлов?

281

Я новичок в угловой. Я пытаюсь прочитать путь к загруженному файлу из поля «файл» HTML всякий раз, когда в этом поле происходит «изменение». Если я использую 'onChange', он работает, но когда я использую его угловым способом, используя 'ng-change', он не работает.

<script>
   var DemoModule = angular.module("Demo",[]);
   DemoModule .controller("form-cntlr",function($scope){
   $scope.selectFile = function()
   {
        $("#file").click();
   }
   $scope.fileNameChaged = function()
   {
        alert("select file");
   }
});
</script>

<div ng-controller="form-cntlr">
    <form>
         <button ng-click="selectFile()">Upload Your File</button>
         <input type="file" style="display:none" 
                          id="file" name='file' ng-Change="fileNameChaged()"/>
    </form>  
</div>

fileNameChaged () никогда не вызывает. Firebug также не показывает никаких ошибок.

Маниш Кумар
источник
1
Может быть полезно: stackoverflow.com/q/17063000/983992
Марсель Гвердер
1
Смотрите также этот stackoverflow.com/questions/16631702/file-pick-with-angular-js
tliokos

Ответы:

240

Нет обязательной поддержки для контроля загрузки файлов

https://github.com/angular/angular.js/issues/1375

<div ng-controller="form-cntlr">
        <form>
             <button ng-click="selectFile()">Upload Your File</button>
             <input type="file" style="display:none" 
                id="file" name='file' onchange="angular.element(this).scope().fileNameChanged(this)" />
        </form>  
    </div>

вместо того

 <input type="file" style="display:none" 
    id="file" name='file' ng-Change="fileNameChanged()" />

ты можешь попробовать

<input type="file" style="display:none" 
    id="file" name='file' onchange="angular.element(this).scope().fileNameChanged()" />

Примечание: для этого необходимо, чтобы угловое приложение всегда находилось в режиме отладки . Это не будет работать в рабочем коде, если режим отладки отключен.

и в твоей функции меняется вместо

$scope.fileNameChanged = function() {
   alert("select file");
}

ты можешь попробовать

$scope.fileNameChanged = function() {
  console.log("select file");
}

Ниже приведен один из рабочих примеров загрузки файлов с возможностью перетаскивания. Http://jsfiddle.net/danielzen/utp7j/

Угловая информация о загрузке файла

URL для загрузки файла AngularJS в ASP.Net

http://cgeers.com/2013/05/03/angularjs-file-upload/

Нативная многофайловая загрузка AngularJ с прогрессом с NodeJS

http://jasonturim.wordpress.com/2013/09/12/angularjs-native-multi-file-upload-with-progress/

ngUpload - Сервис AngularJS для загрузки файлов с использованием iframe

http://ngmodules.org/modules/ngUpload

JQuery Гуру
источник
2
onchange = "angular.element (this) .scope (). fileNameChaged (this)" $ scope.fileNameChaged = function (element) {console.log ('files:', element.files); } Можете ли вы поменять в двух местах и ​​проверить? в зависимости от браузера вы получите полный путь к файлу, который я обновил. Ниже приведена ссылка для
JQuery Guru
13
Этот метод обходит обработку AngularJS по умолчанию, поэтому $ scope. $ Digest () не вызывается (привязки не обновляются). Затем вызовите angular.element (this) .scope (). $ Digest () или вызовите метод области действия, который использует $ apply.
Рамон де Кляйн
113
Это ужасный ответ. Вызов angular.element (blah) .scope () - это функция отладки, которую вы должны использовать только для отладки. Angular не использует функцию .scope. Это только там, чтобы помочь разработчикам отладить. Когда вы создаете свое приложение и внедряете его в производство, вы должны отключить отладочную информацию. Это ускоряет все ваши много! Поэтому писать код, который зависит от вызовов element.scope (), - плохая идея. Не делай этого. Ответ о создании пользовательской директивы - правильный способ сделать это. @JQueryGuru вы должны обновить свой ответ. Поскольку у него больше всего голосов, люди последуют этому дурному совету.
морозное
7
Это решение сломает приложение, когда отладочная информация отключена. Отключение этого для производства является официальной рекомендацией docs.angularjs.org/guide/production . Пожалуйста, смотрите также blog.thoughtram.io/angularjs/2014/12/22/…
wiherek
4
ПЛОХОЕ РЕШЕНИЕ ... ... читать другие комментарии о «отключении отладки» ... ... ... и посмотреть sqrenрешение ... ... ... @ 0MV1
dsdsdsdsd
477

Я сделал небольшую директиву для прослушивания изменений ввода файла.

Посмотреть JSFiddle

view.html:

<input type="file" custom-on-change="uploadFile">

controller.js:

app.controller('myCtrl', function($scope){
    $scope.uploadFile = function(event){
        var files = event.target.files;
    };
});     

directive.js:

app.directive('customOnChange', function() {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      var onChangeHandler = scope.$eval(attrs.customOnChange);
      element.on('change', onChangeHandler);
      element.on('$destroy', function() {
        element.off();
      });

    }
  };
});
sqren
источник
4
Это хорошо работает, если каждый раз выбирается другой файл. Можно ли слушать, когда выбран один и тот же файл?
JDawg
12
У этого есть одна очень печальная «ошибка» - контроллер не привязан как «this» в customOnChange, а к файлу ввода! Это сбило меня с толку в течение долгого времени, и я не мог найти фактическую ошибку. Я еще не уверен, как на самом деле назвать его с правильно установленным «это».
Карел Билек
4
у меня скрипка не работает ни на Chrome, ни на Firefox на сегодняшний день.
Сегусо
2
Этот ответ ошибочен, как заявлено @ KarelBílek. Я не мог обойти это и решил использовать angular-file-upload.
Гунар Гесснер
2
Это абсолютно, как это сделать, работал как шарм. Кроме того, зачем вам использовать функцию отладки в производстве?
Джастин Крузе
42

Это уточнение некоторых других, данные в конечном итоге окажутся в ng-модели, которая обычно является тем, что вы хотите.

Разметка (просто создайте файл данных атрибута, чтобы директива могла его найти)

<input
    data-file
    id="id_image" name="image"
    ng-model="my_image_model" type="file">

JS

app.directive('file', function() {
    return {
        require:"ngModel",
        restrict: 'A',
        link: function($scope, el, attrs, ngModel){
            el.bind('change', function(event){
                var files = event.target.files;
                var file = files[0];

                ngModel.$setViewValue(file);
                $scope.$apply();
            });
        }
    };
});
Стюарт Аксон
источник
1
Является ли $scope.$apply();требуется? Мое ng-changeсобытие до сих пор, кажется, без него.
row1
1
@ row1, вам нужно только вручную запустить заявку, если у вас есть другие угловатые вещи, которые нужно знать об этом во время этой операции.
Скотт Меч
27

Чистый способ - написать собственную директиву для привязки к событию «change». Просто чтобы вы знали, что IE9 не поддерживает FormData, поэтому вы не можете получить объект file из события change.

Вы можете использовать библиотеку ng-file-upload, которая уже поддерживает IE с полиафиллом FileAPI и упрощает отправку файла на сервер. Для этого используется директива.

<script src="angular.min.js"></script>
<script src="ng-file-upload.js"></script>

<div ng-controller="MyCtrl">
  <input type="file" ngf-select="onFileSelect($files)" multiple>
</div>

JS:

//inject angular file upload directive.
angular.module('myApp', ['ngFileUpload']);

var MyCtrl = [ '$scope', 'Upload', function($scope, Upload) {
  $scope.onFileSelect = function($files) {
    //$files: an array of files selected, each file has name, size, and type.
    for (var i = 0; i < $files.length; i++) {
      var $file = $files[i];
      Upload.upload({
        url: 'my/upload/url',
        data: {file: $file}
      }).then(function(data, status, headers, config) {
        // file is uploaded successfully
        console.log(data);
      }); 
    }
  }
}];
Даниал
источник
Этот пример устарел. Получить новый из документов здесь .
Гунар Гесснер
26

Я расширил идею @Stuart Axon по добавлению двухсторонней привязки для ввода файла (т. Е. Разрешить сброс ввода путем сброса значения модели обратно на ноль):

app.directive('bindFile', [function () {
    return {
        require: "ngModel",
        restrict: 'A',
        link: function ($scope, el, attrs, ngModel) {
            el.bind('change', function (event) {
                ngModel.$setViewValue(event.target.files[0]);
                $scope.$apply();
            });

            $scope.$watch(function () {
                return ngModel.$viewValue;
            }, function (value) {
                if (!value) {
                    el.val("");
                }
            });
        }
    };
}]);

демонстрация

JLRishe
источник
Спасибо @JLRishe. Одна заметка, на самом деле data-ng-click="vm.theFile=''"работает хорошо, нет необходимости записывать ее в метод контроллера
Сергей
20

Подобно некоторым другим хорошим ответам здесь, я написал директиву для решения этой проблемы, но эта реализация более близко отражает угловой способ прикрепления событий.

Вы можете использовать директиву так:

HTML

<input type="file" file-change="yourHandler($event, files)" />

Как видите, вы можете вставить файлы, выбранные в ваш обработчик событий, так же, как вы вставили бы объект $ event в любой обработчик событий ng.

Javascript

angular
  .module('yourModule')
  .directive('fileChange', ['$parse', function($parse) {

    return {
      require: 'ngModel',
      restrict: 'A',
      link: function ($scope, element, attrs, ngModel) {

        // Get the function provided in the file-change attribute.
        // Note the attribute has become an angular expression,
        // which is what we are parsing. The provided handler is 
        // wrapped up in an outer function (attrHandler) - we'll 
        // call the provided event handler inside the handler()
        // function below.
        var attrHandler = $parse(attrs['fileChange']);

        // This is a wrapper handler which will be attached to the
        // HTML change event.
        var handler = function (e) {

          $scope.$apply(function () {

            // Execute the provided handler in the directive's scope.
            // The files variable will be available for consumption
            // by the event handler.
            attrHandler($scope, { $event: e, files: e.target.files });
          });
        };

        // Attach the handler to the HTML change event 
        element[0].addEventListener('change', handler, false);
      }
    };
  }]);
Саймон Робб
источник
боролся с этим пару дней, пока не попробовал ваш ответ. Спасибо!
Майкл Транчида
Как я могу передать дополнительный параметр в fileChangeвыражение? Я использую эту директиву внутри ng-repeatи $scopeпеременная передается attrHandlerодинаково для каждой записи. Какое лучшее решение?
16

Эта директива также передает выбранные файлы:

/**
 *File Input - custom call when the file has changed
 */
.directive('onFileChange', function() {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      var onChangeHandler = scope.$eval(attrs.onFileChange);

      element.bind('change', function() {
        scope.$apply(function() {
          var files = element[0].files;
          if (files) {
            onChangeHandler(files);
          }
        });
      });

    }
  };
});

HTML, как его использовать:

<input type="file" ng-model="file" on-file-change="onFilesSelected">

В моем контроллере:

$scope.onFilesSelected = function(files) {
     console.log("files - " + files);
};
ingaham
источник
Как выглядит $ scope.onFilesSelected в вашем контроллере?
Ингаам
Если файл открыт и когда мы пытаемся загрузить файл в браузере Microsoft Edge. Ничего не происходит. Вы можете помочь?
Навин Катакам
Да, он отлично работает с другими браузерами. Я сталкиваюсь с проблемой в Microsoft Edge @ingaham
Навин Катакам
8

Я рекомендую создать директиву

<input type="file" custom-on-change handler="functionToBeCalled(params)">

app.directive('customOnChange', [function() {
        'use strict';

        return {
            restrict: "A",

            scope: {
                handler: '&'
            },
            link: function(scope, element){

                element.change(function(event){
                    scope.$apply(function(){
                        var params = {event: event, el: element};
                        scope.handler({params: params});
                    });
                });
            }

        };
    }]);

эта директива может использоваться много раз, она использует свою собственную область и не зависит от родительской области. Вы также можете передать некоторые параметры функции-обработчику. Функция-обработчик будет вызываться с объектом области видимости, который был активен при изменении ввода. $ apply обновляет вашу модель каждый раз, когда вызывается событие изменения

Юлия Усанова
источник
4

Простейшая версия Angular jqLite.

JS:

.directive('cOnChange', function() {
    'use strict';

    return {
        restrict: "A",
        scope : {
            cOnChange: '&'
        },
        link: function (scope, element) {
            element.on('change', function () {
                scope.cOnChange();
        });
        }
    };
});

HTML:

<input type="file" data-c-on-change="your.functionName()">
Йонаш Крутил
источник
Если файл открыт и когда мы пытаемся загрузить файл в браузере Microsoft Edge. Ничего не происходит. Вы можете помочь?
Навин Катакам
2

Рабочая демонстрация директивы «file-input», которая работает с ng-change1

Чтобы заставить <input type=file>элемент работать с директивой ng-change , ему нужна пользовательская директива, которая работает с директивой ng-model .

<input type="file" files-input ng-model="fileList" 
       ng-change="onInputChange()" multiple />

ДЕМО

angular.module("app",[])
.directive("filesInput", function() {
  return {
    require: "ngModel",
    link: function postLink(scope,elem,attrs,ngModel) {
      elem.on("change", function(e) {
        var files = elem[0].files;
        ngModel.$setViewValue(files);
      })
    }
  }
})

.controller("ctrl", function($scope) {
     $scope.onInputChange = function() {
         console.log("input change");
     };
})
<script src="//unpkg.com/angular/angular.js"></script>
  <body ng-app="app" ng-controller="ctrl">
    <h1>AngularJS Input `type=file` Demo</h1>
    
    <input type="file" files-input ng-model="fileList" 
           ng-change="onInputChange()" multiple />
    
    <h2>Files</h2>
    <div ng-repeat="file in fileList">
      {{file.name}}
    </div>
  </body>

georgeawg
источник
Замечательно, но директива вызывается только один раз! Если я изменяю выбранный файл, то директива не вызывается во второй раз! Есть ли у вас какие-либо идеи об этом поведении?
hugsbrugs
ДЕМО не имеет этой проблемы. Создайте новый вопрос с воспроизводимым примером для читателей.
georgeawg
да, есть такая проблема: консольный журнал «изменение ввода» отображается только один раз, даже если вы много раз меняли выбранные файлы!
hugsbrugs
после тестирования он работает на хроме, но не на последней версии Firefox ... чертовски совместимость браузера !!!
hugsbrugs
0

Слишком полное решение основано на:

`onchange="angular.element(this).scope().UpLoadFile(this.files)"`

Простой способ скрыть поле ввода и заменить его изображением, здесь после решения, которое также требует взлома angular, но выполняет свою работу [TriggerEvent не работает должным образом]

Решение:

  • поместите поле ввода на дисплее: нет [поле ввода существует в DOM, но не видно]
  • поместите свое изображение сразу после На изображении используйте nb-click (), чтобы активировать метод

При нажатии на изображение имитирует действие DOM «щелкнуть» в поле ввода. И вуаля!

 var tmpl = '<input type="file" id="{{name}}-filein"' + 
             'onchange="angular.element(this).scope().UpLoadFile(this.files)"' +
             ' multiple accept="{{mime}}/*" style="display:none" placeholder="{{placeholder}}">'+
             ' <img id="{{name}}-img" src="{{icon}}" ng-click="clicked()">' +
             '';
   // Image was clicked let's simulate an input (file) click
   scope.inputElem = elem.find('input'); // find input in directive
   scope.clicked = function () {
         console.log ('Image clicked');
         scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
    };
Fulup
источник
1
Вы не можете использовать angular.element (this) .scope (), это функция отладки!
Цезарь
0

Я сделал это так;

<!-- HTML -->
<button id="uploadFileButton" class="btn btn-info" ng-click="vm.upload()">    
<span  class="fa fa-paperclip"></span></button>
<input type="file" id="txtUploadFile" name="fileInput" style="display: none;" />
// self is the instance of $scope or this
self.upload = function () {
   var ctrl = angular.element("#txtUploadFile");
   ctrl.on('change', fileNameChanged);
   ctrl.click();
}

function fileNameChanged(e) {
    console.log(self.currentItem);
    alert("select file");
}
Али Сахи
источник
0

Другой интересный способ прослушивания изменений ввода файла - наблюдение за атрибутом ng-model входного файла. Конечно, FileModel - это пользовательская директива.

Как это:

HTML -> <input type="file" file-model="change.fnEvidence">

Код JS ->

$scope.$watch('change.fnEvidence', function() {
                    alert("has changed");
                });

Надеюсь, что это может кому-то помочь.

halbano
источник
0

Угловые элементы (такие как корневой элемент директивы) являются объектами jQuery [Lite]. Это означает, что мы можем зарегистрировать прослушиватель событий следующим образом:

link($scope, $el) {
    const fileInputSelector = '.my-file-input'

    function setFile() {
        // access file via $el.find(fileInputSelector).get(0).files[0]
    }

    $el.on('change', fileInputSelector, setFile)
}

Это делегирование события jQuery. Здесь слушатель присоединен к корневому элементу директивы. Когда событие инициируется, оно всплывет до зарегистрированного элемента, и jQuery определит, произошло ли событие на внутреннем элементе, соответствующем определенному селектору. Если это произойдет, обработчик сработает.

Преимущества этого метода:

  • обработчик привязан к элементу $, который будет автоматически очищен при уничтожении области действия директивы.
  • нет кода в шаблоне
  • будет работать, даже если целевой делегат (входной) еще не был обработан при регистрации обработчика события (например, при использовании ng-ifили ng-switch)

http://api.jquery.com/on/

Мэтт С
источник
-1

Вы можете просто добавить приведенный ниже код в onchange, и он обнаружит изменения. Вы можете написать функцию на X click или что-то, чтобы удалить данные файла ..

document.getElementById(id).value = "";
Джиджо Томас
источник
Это плохая практика, а не угловатая.
Себастьян