0

I have this html

<input type="file" file-input="files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in files">{{file.name}}</li>

This directive:

.directive('fileInput', ['$parse', function($parse){
    return {
        restrict: 'A',
        link: function(scope, elm, attrs){
            //console.log("directives scope: ")

            elm.bind('change', function(){

                $parse(attrs.fileInput)
                .assign(scope,elm[0].files)
                scope.$apply()
                //console.log(scope);
            })
        }
    }
}]);

and in my controller I have this function:

$scope.upload=function(){
    console.log($scope.files)
    var fd = new FormData() // put these 3 lines in a service
    angular.forEach($scope.files, function(file){
      fd.append('file', file)
    })
    $http.post('/api/directory/upload-file-test', fd,
    {
      transformRequest: angular.identity, // returns first argument it is passed
      headers:{'Content-Type': undefined} //multipart/form-data
    })
    .success(function(d){
      console.log(d)
      console.log("works?")
    })
  }

It works fine if I just put the HTML directly in the html file as you'd normally do, however...

I need to inject it, and when I do that, the directive scope and controller scope is not the same.. so files which I've added to scope.files in the directive is just "undefined" inside the controller function, so my file upload breaks...

More exactly...

If I do this:

<tr ng-repeat="prop in tab.properties">
  <td>{{prop.name}}</td>
  <td compile ng-bind-html="prop.data_type.html | unsafe"></td>
  <td>{{prop.description}}</td>
</tr>

Where the content inside the ng-bind-html quotes (prop.data_type.html) simply just equals to this:

<input type="file" file-input="files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in files">{{file.name}}</li>

It doesn't work. The scopes are different.

My compile-directive looks like this:

.directive('compile',function($compile, $timeout){
    return{
        restrict:'A',
        link: function(scope,elem,attrs){
            $timeout(function(){                
                $compile(elem.contents())(scope);    
            });
        }        
    };
})

and the last relevant bit of code would be the unsafe-filter which is this:

.filter('unsafe', function($sce) {
    return function(val) {
        return $sce.trustAsHtml(val);
    };
})

Does anyone have an idea why my "upload" function inside my controller and my directive scope cannot stay synced and reference the same scope IF and only IF I inject my html with my compile-directive and unsafe-filter via ng-bind-html? Is there anyway around this or must I refrain from using directives to make this work?

I've tried first Angular 1.3.0 rc4 and now after I upgraded to latest version v. 1.3.5 it's still the same.

Dac0d3r
  • 2,176
  • 6
  • 40
  • 76
  • Why can't you use `ng-include` instead of `ng-bind-html` + custom `compile` directive? – New Dev Dec 05 '14 at 22:46
  • I can understand your question since I didn't show you that the ng-bind-html line is actually inside a ng-repeat "loop" (updated the code snippit), and you have to imagine that ever element contains a "Data Type"'s html markup. They are all different, so beginning to make templates and include these wouldn't be a valid solution. – Dac0d3r Dec 05 '14 at 22:56
  • If you tried placing your three lines of content (that you tried to import via `ng-bind-html`) directly within `ng-repeat` you'd get the same effect. In other words, it's not the `compile` directive - it's `ng-repeat` creating child scopes for each iteration and your `files` is assigned to that child scope. – New Dev Dec 06 '14 at 00:48
  • You should look into creating an IsolateScope for your directives if you intend to use them multiple times in your page. – New Dev Dec 06 '14 at 00:48
  • I don't intend to use it more than once per page. There's only 1 file uploader per page, but thanks for trying to point me in the right direction - much appreciated. If I find a solution I'll post it here. :) – Dac0d3r Dec 06 '14 at 00:55
  • The proper solution is to use an isolateScope with the directive, and deal with scope inheritance (if you must) in page controllers. A quick and dirty fix could be to use `scope.$root` instead of `scope` in the `assign` function. – New Dev Dec 06 '14 at 01:26
  • Thanks, I guess that would work as well, but I ended up with a tiny correction and posted the solution below. Thank you very much for this alternative solution as well, and for leading me on the right path, telling me it was a problem with ng-repeat and not ng-bind-html. :) – Dac0d3r Dec 06 '14 at 02:09

1 Answers1

0

I was able to solve this, and here's the simple solution:

(old code):

<input type="file" file-input="files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in files">{{file.name}}</li>

Replaced with this:

<input type="file" file-input="test.files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in test.files">{{file.name}}</li>

And this old code:

.directive('fileInput', ['$parse', function($parse){
    return {
        restrict: 'A',
        link: function(scope, elm, attrs){
            //console.log("directives scope: ")

            elm.bind('change', function(){

                $parse(attrs.fileInput)
                .assign(scope,elm[0].files)
                scope.$apply()
                //console.log(scope);
            })
        }
    }
}]);

Should be replaced with this:

.directive('fileInput', ['$parse', function($parse){
    return {
        restrict: 'A',
        link: function(scope, elm, attrs){
            if(typeof(scope.test) == undefined){
              scope.test = { "files": []}
            }
            if(typeof(scope.test.files) !== undefined){
              scope.test["files"] =[]
            }
            elm.bind('change', function(){

                $parse(attrs.fileInput)
                .assign(scope,elm[0].files)
                scope.$apply()
            })
        }
    }
}]);

And the same with the controller function (old code first):

$scope.upload=function(){
    console.log($scope.files)
    var fd = new FormData() // put these 3 lines in a service
    angular.forEach($scope.files, function(file){
      fd.append('file', file)
    })
    $http.post('/api/directory/upload-file-test', fd,
    {
      transformRequest: angular.identity, // returns first argument it is passed
      headers:{'Content-Type': undefined} //multipart/form-data
    })
    .success(function(d){
      console.log(d)
      console.log("works?")
    })
  }

Solution:

  $scope.test = {files:undefined}

  $scope.upload=function(){
    console.log($scope.test.files)
    var fd = new FormData() // put these 3 lines in a service
    angular.forEach($scope.test.files, function(file){
      fd.append('file', file)
    })
    $http.post('/api/directory/upload-file-test', fd,
    {
      transformRequest: angular.identity, // returns first argument it is passed
      headers:{'Content-Type': undefined} //multipart/form-data
    })
    .success(function(d){
      console.log(d)
      console.log("works?")
    })
  }

If you need more explanation, the wise man Josh David Miller have it here. This was the comment that made me realize how to solve this problem: https://stackoverflow.com/a/15645354/3973406

Basically it has nothing to do with isolate scope, but because we were breaking a rule and Angular being a bitch about it!

Community
  • 1
  • 1
Dac0d3r
  • 2,176
  • 6
  • 40
  • 76