asafdav/ng-s3upload

Accessing S3 file url after successful upload

btoone opened this issue · 6 comments

Currently the only way I can figure out how to access the S3 file url is to have a $watch on the model in my controller.

It seems like a better solution would be to have the the event s3upload:success include the file url as an additional argument. Then I could simple add a listener in my controller and do whatever I wanted with the file url.

Is there already a better way to access the file url from the controller? If not is adding it to the events a good option?

hi @caspyin are you able to share a gist of how you accessed the s3 file url using a $watch? I can't seem to pass a variable assigned with the url value from within a $watch function (only propogates a string I believe)...quite frustrating as this module is great in what it does but becomes rather redundant without a way to access the file in the server response callback.

@juliussss Can you show the code you have now and maybe I can help you out?

I have mine being returned to a promise after the upload takes place and then you have all the data returned for the current upload. The trick is parsing it from XML to JSON so you can store it in a scope variable. Something like this:

scope.upload[i].then(function(response) {
     if (response.status === 201) {
          $scope.fileUploading = false;
          var data = xml2json.parser(response.data);
          var parsedData = {
               location: data.postresponse.location,
               bucket: data.postresponse.bucket,
               key: data.postresponse.key,
               etag: data.postresponse.etag
          };
          messageService.newMessage('Your photo uploaded successfully.','success',5);
          $scope.imageUploads.push(parsedData);
          $scope.saveS3Photo($scope.imageUploads);
          $scope.nextPhoto();
     } else {
          messageService.newMessage('There was an error uploading your image.  Please try again later.','error',5);
     }

As you can see, I'm taking the response from the upload, parsing it, then pushing that parseData directly into the $scope.imageUploads variable. It instantly becomes available to use inside your HTML at that point. Mine is for an image so I show the user their image they uploaded with a success message.

Hey @onorok thanks for the reply...

To illustrate my setup — I have a userprofile.html view which contains a form with multiple fields (name, email, avatar, etc) that is populated from properties of the current logged in user. I am using the ng-s3Upload module for the avatar image upload part of the form. Here's a snippet:

<form name="user_settings" ng-controller="EditUserCtrl">
 <div>
  <label for="fullname">Full Name</label>
  <input name="fullname" type="text" ng-model="user.full_name" value="{{ user.full_name }}">
 </div>
 <div>
  <label for="photo">Photo (s3 location: {{ userToUpdate.avatar_location }})</label>
  <div s3-upload bucket="'s3BucketName'" ng-model="user.avatar_location" s3-upload-options="{getOptionsUri: '/upload', folder: 'uploads/', enableValidation: 'false'}"></div>
 </div>
   <!-- // more form fields -->
 <div>
  <input type="submit" value="Submit" ng-click="updateUser()" />
 </div>
</form>

This view uses a controller named EditUserCtrl which is obviously different to the controller defined in the ng-s3Upload module / directive. The following is a snippet from this EditUserCtrl controller which attempts to capture the uploaded s3 file URL, assign it to a variable and then update the user property accordingly before sending back to the server.

// when a new file is uploaded assign the new s3 url to the $scope.s3Url variable
$scope.$watch('user.avatar_location', function(newVal, oldVal) {
  $scope.s3Url = newVal;
  console.log(oldVal, newVal);
});     

// submit button function to update the user
$scope.updateUser = function() {
  // use the URL location that was assigned in the $watch function
  $scope.user.avatar_location = $scope.s3Url; 
  // save the user settings using the service
  usersService.update({id: $scope.user.id}, $scope.user, function() {
   // redirect on success
   $window.location.href = '/users/' + $scope.currentUser.id;
  }, function(error) {
   // log error
  });
};

The reason for trying to tackle the problem this way is because the ng-s3Upload directive uploads the file instantly (i.e. it does not seem to have an option to upload the file only when clicking the form's submit button, which then means I do not have an s3 server response to extract the URL from as would be preferred), so I try to capture the s3 file URL by implementing a $watch function but sadly this also fails.

Hopefully this makes sense. Happy to give more clarity if needed and thanks again for trying to help!

@juliussss I think I had the same problem with the $watch fn. It has been a while but it seems like there was a timing issue.

Anyway I ended up adding to the uploadComplete() fn to emit the file url on success. Then I just use an event listener in my controller to do what I want with the file url.

// ng-s3upload.js (local hack)
function uploadComplete(e) {
  var xhr = e.srcElement || e.target;
  scope.$apply(function () {
    self.uploads--;
    scope.uploading = false;
    if (xhr.status === 204) { // successful upload
      scope.success = true;
      deferred.resolve(xhr);

      // Modified to include the file url
      scope.$emit('s3upload:success', xhr, uri + key);

    } else {
      scope.success = false;
      deferred.reject(xhr);
      scope.$emit('s3upload:error', xhr);
    }
  });
}
// controller.js
$scope.$on('s3upload:success', function (evt, xhr, fileUrl) {

  // do stuff here with fileUrl
})

$scope.$on('s3upload:error', function (evt, xhr) {
  // alert the failure
})

Hi all, sorry for my late response, my son was born few weeks ago and it's been hectic lately.

@caspyin I liked you solution, and implemented it as part as the library. Thanks a lot !

Thanks @caspyin, works wonderfully 👍