3

I need to upload image and video files to the server in an Angular application using Laravel 5.1 as the back end. All Ajax requests need to go to the Laravel controller first, and we have the code there for how the file gets handled when it gets there. We have previously done normal HTML forms to submit file uploads to the controller, but in this case we need to avoid the page refresh of a form, so I am attempting this in Ajax through Angular.

What information do I need to send to the Laravel controller with Ajax that was being sent to the controller via an HTML form previously?

This is the code in the Laravel controller that handled the file information once it got there. That's what I need to figure out how to send, so I can hopefully reuse this code:

    $promotion = Promotion::find($id);
    if (Input::hasFile('img_path')){
        $path             = public_path().'/images/promotion/'.$id.'/';
        $file_path        = $path.'promotion.png';
        $delete           = File::delete($file_path);
        $file             = Input::file('img_path');
        $uploadSuccess    = $file->move($path, 'promotion.png');
        $promotion->img_path = '/images/promotion/'.$id.'/promotion.png';
    }
    if (Input::hasFile('video_path')){
        $path             = public_path().'/video/promotion/'.$id.'/';
        $file_path        = $path.'promotion.mp4';
        $delete           = File::delete($file_path);
        $file             = Input::file('video_path');
        $uploadSuccess    = $file->move($path, 'promotion.mp4');
        $promotion->video_path = '/video/promotion/'.$id.'/promotion.mp4';
    }

As you can see above, we are converting whatever file we get to a PNG with the file name promotion.png so it's easy to fetch, and we are only accepting .mp4 video format. Because of that, we don't need to worry about checking if the file exists and is it ok to overwrite it. That's why you can see in the code we delete any existing file of that name before saving.

The HTML was just an input with a type of "file:

<input type="file" id="img_path" name="img_path" class="promo-img-path" accept="image/*">

We are using Angular now so I can't just send the above through an HTML form anymore. That's what I need to figure out how to do.

We are two developers just doing our best, so I'm sure there is a better way of doing this. However before I refactor this whole thing, I'm hoping I can use Angular (or jQuery as a last resort) to just send the controller whatever file data Laravel needs in order to make the above code work. The answer may be as simple as "send a PUT to the method in that controller above, but instead of a normal JSON payload, use file info in this format and you can gather that info with..."

I would also appreciate any tips on better ways I can do this in the future.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
SpaceNinja
  • 496
  • 7
  • 20
  • Best advice I can give is to not reinvent the wheel. There's plenty of existing Angular modules for file uploads - using one will usually save you a lot of grief. – Matthew Daly Jan 19 '17 at 20:29
  • But those all upload directly somewhere - they aren't intended to send file data to a Laravel controller. – SpaceNinja Jan 19 '17 at 20:33
  • Are you sending the files via PUT alongside regular input? – Matthew Daly Jan 19 '17 at 20:38
  • It was being sent in a normal
    to the above controller. Now I need to use an Ajax PUT through Angular. I just need to know how to send whatever that controller needs in a PUT, to simulate whatever gets sent from an
    – SpaceNinja Jan 19 '17 at 20:41
  • If I recall correctly the $_FILE array is not populated for a PUT request in PHP. You need to use POST, but you can set an additional parameter in the request of _method=PUT to tell Laravel it should be treated as a PUT request. – Matthew Daly Jan 19 '17 at 20:52
  • Ok I'm sure it can be a POST. So you're saying I need to include a $_FILE array in the POST and it will work to the above controller? – SpaceNinja Jan 19 '17 at 20:59
  • I can't guarantee that's the issue but I've experienced it before and it sounds likely. I would try submitting the file directly to the controller using Postman and see if adding the _method parameter and submitting it via POST works. – Matthew Daly Jan 19 '17 at 21:10
  • Regardless, I need to create an Ajax call in Angular (not jQuery) to send the file contents (or file data or whatever is needed) to the above Laravel controller. Whether it works in Postman or not, I need to do it in Angular. If anyone could suggest the syntax for this, I would be very appreciative. Everything I see online is either a directive that saves the file (not what I need) or is jQuery (not what I need). – SpaceNinja Jan 19 '17 at 22:04
  • The reason I suggested trying it in Postman is that doing so will indicate whether the problem is with theJavascript itself or how it's being submitted. If you can't find the source of the problem you can't fix it. Postman is a debugging tool, not something you use in production. – Matthew Daly Jan 19 '17 at 22:08
  • Well I do appreciate the guidance. I'm still in the idea phase of how I'm going to code this. I have never sent image data across Ajax to a Laravel controller before so I don't know what the POST will look like. You mentioned $_FILE but searching for that returns articles with jQuery. Some mention FormData, I'm looking into that. But it feels like the answer should be simple, it's just very unusual to be doing this I guess. – SpaceNinja Jan 19 '17 at 22:23
  • 1
    $_FILE is the array where PHP stores any submitted files. If you're using a framework you generally don't work with it directly as the framework will usually provide another way. I would build the route for receiving the image first and make sure you can submit an image using something like Postman. Once that's done you then just have to make exactly the same request via AJAX. – Matthew Daly Jan 19 '17 at 22:34

1 Answers1

6

How to POST FormData Using the $http Service

When using the FormData API to POST files and data, it is important to set the Content-Type header to undefined.

var fd = new FormData()
for (var i in $scope.files) {
    fd.append("fileToUpload", $scope.files[i]);
}
var config = {headers: {'Content-Type': undefined}};

var httpPromise = $http.post(url, fd, config);

By default the AngularJS framework uses content type application/json. By setting Content-Type: undefined, the AngularJS framework omits the content type header allowing the XHR API to set the content type. When sending a FormData object, the XHR API sets the content type to multipart/form-data with the proper boundaries and base64 encoding.

For more information, see MDN Web API Reference - XHR Send method


How did you get the file information into $scope.files?

How to enable <input type="file"> to work with ng-model

This directive also enables <input type="file"> to automatically work with the ng-change and ng-form directives.

angular.module("app",[]);

angular.module("app").directive("selectFilesNg", function() {
  return {
    require: "ngModel",
    link: function postLink(scope,elem,attrs,ngModel) {
      elem.on("change", function(e) {
        var files = elem[0].files;
        ngModel.$setViewValue(files);
      })
    }
  }
});
<script src="//unpkg.com/angular/angular.js"></script>
  <body ng-app="app">
    <h1>AngularJS Input `type=file` Demo</h1>
    
    <input type="file" select-files-ng ng-model="fileArray" multiple>

    <code><table ng-show="fileArray.length">
    <tr><td>Name</td><td>Date</td><td>Size</td><td>Type</td><tr>
    <tr ng-repeat="file in fileArray">
      <td>{{file.name}}</td>
      <td>{{file.lastModified | date  : 'MMMdd,yyyy'}}</td>
      <td>{{file.size}}</td>
      <td>{{file.type}}</td>
    </tr>
    </table></code>
    
  </body>

RECOMMENDED: POST Binary Files Directly

Posting binary files with multi-part/form-data is inefficient as the base64 encoding adds an extra 33% overhead. If the server API accepts POSTs with binary data, post the file directly.

See How to POST binary files with AngularJS (with DEMO)

Community
  • 1
  • 1
georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • Looks good. But for some reason the way I'm doing it the file information doesn't get appended to the FormData object so I'll try your way. I'm already doing `headers: { 'Content-Type': undefined }` This isn't working: `angular.forEach($files, function (value, key) { formdata.append(key, value); });` How did you get the file information into $scope.files? – SpaceNinja Jan 20 '17 at 02:36
  • @georgeawg can you please explain how to get the file data in laravel controller. – Kannan K Apr 18 '18 at 08:08
  • @KannanK see [Laravel storing only one file from multiple file upload file using AngularJS](https://stackoverflow.com/questions/50948228/laravel-storing-only-one-file-from-multiple-file-upload-file-using-angularjs/50948561#50948561). – georgeawg Jun 20 '18 at 15:34
  • When only one file is selected the loop `for (var i in $scope.files) {}` lappends the FileList length property too. I instead used `for (var i = 0; i < $scope.files.lenght; i++) {}`. for/in used above will loop through all FileList properites even if its not a File https://www.w3schools.com/jsref/jsref_forin.asp – MosesSoftEng Jul 11 '21 at 20:57