Håkan Fahlstedt
Håkan Fahlstedt

Reputation: 2095

Why does Angular binding fail when using <input type="file"/>?

I'm far from an expert on Angular, so I have to try asking a question here. I've added the possibility to upload files in may AngularJS project. I need to display the selected filename in a textbox (or preferably a read-only field) before the user submits the form. It contains a number of fields that need validation. The problem is, the selected filename never shows up. The textblock is still empty after selecting a file. I've tried the following code (Plunker: http://plnkr.co/edit/Ll3GZdpp8Tsqwvo0W1ax):

index.html:

<!DOCTYPE html>
<html ng-app="myApp">

<head lang="en">
  <meta charset="utf-8" />
  <title>Custom Plunker</title>
  <script data-require="[email protected]" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
  <link data-require="bootstrap@*" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
  <script data-require="bootstrap@*" data-semver="3.3.2" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
  <link rel="stylesheet" type="text/css" href="http://angular-ui.github.com/ng-grid/css/ng-grid.css" />
  <link rel="stylesheet" type="text/css" href="style.css" />
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
  <script type="text/javascript" src="http://angular-ui.github.com/ng-grid/lib/ng-grid.debug.js"></script>
  <script type="text/javascript" src="main.js"></script>
</head>

<body ng-controller="MyCtrl">

  <div class="row">
    <div class="col-md-12">
      <span class="btn btn-default btn-file">
              Välj fil... <input type="file" onchange=" angular.element(this).scope().setFile(this) ">
                                        </span>
      <input type="text" ng-model="organizationSettings.logotypeFileName" />
    </div>
  </div>
</body>

</html>

app.js:

var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
  $scope.organizationSettings = {};

  $scope.setFile = function(element) {
    $scope.fileToUpload = element.files[0];
    console.log($scope.fileToUpload.name);
    $scope.organizationSettings.logotypeFileName = $scope.fileToUpload.name;
  };    
});

css:

.btn-file {
    position: relative;
    overflow: hidden;
}
.btn-file input[type=file] {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 100%;
    min-height: 100%;
    font-size: 100px;
    text-align: right;
    filter: alpha(opacity=0);
    opacity: 0;
    outline: none;
    background: white;
    cursor: inherit;
    display: block;
}

Upvotes: 0

Views: 1319

Answers (3)

Alhuck
Alhuck

Reputation: 1029

You can't use ng-change for file upload because binding is not supported for file upload in angularjs., its an issue in angularjs.

In other cases while you are using ng-change(other than file upload) you have to use ng-model to make the ng-change directive work in order to trigger change based on it(so only you are getting "Error: No controller: ngModel") while using it.

The best possible solution is you manually trigger $scope.$apply() to make binding to the input model while programatically setting it.

Upvotes: 1

Alexandru Nedelcu
Alexandru Nedelcu

Reputation: 267

I think that binding changes could be done easier, and I will explain below how to do it. By setting a ng-model to the input, it will automatically bind the input control to the variable defined in your controller's scope. Therefore, if you specify a new file, it will automatically update the model $scope.fileToUpload. This goes the other way around: any change in $scope.fileToUpload done in JS, will be reflected in the DOM.

index.html - try to replace

<input type="file" onchange=" angular.element(this).scope().setFile(this) ">

with

<input type="file" ng-model="fileToUpload">

In app.js, you could get rid of:

$scope.setFile = function(element) {
    $scope.fileToUpload = element.files[0];
    console.log($scope.fileToUpload.name);
    $scope.organizationSettings.logotypeFileName = $scope.fileToUpload.name;
  };    

Upvotes: 1

charlietfl
charlietfl

Reputation: 171679

The reason is you are using onchange which is an event outside of any of angular's directives.

Whenever you use an event outside of angular that changes the scope, you need to notify that a digest is needed to update the part of the view managed by that scope by using $apply

The simplest fix is to use ng-change instead of native onchange. All of angulars event handling directives will trigger $apply internally

The alternative (not best approach) would be keep the onchange and do:

$scope.setFile = function(element) {  
    $scope.fileToUpload = element.files[0];
    console.log($scope.fileToUpload.name);
    $scope.organizationSettings.logotypeFileName = $scope.fileToUpload.name;

    $scope.$apply();
}; 

Upvotes: 2

Related Questions