I am trying to make an app that uses an angular 4 front end with a rails back end. I am currently trying to figure out how to upload images to my rails back end server using carrierwave. I am very stuck, there is very little information out there on how to implement Angular 4 with rails and carrierwave. I cannot figure out how to get it to upload the image to the rails server, when I inspect the rails console I get the below message. from what I can tell there is nothing being posted for some reason and I do not know why this is. If anyone who knows more about angular and rails could help that would be great.
rails console message when I try to upload image:
Processing by ImagesController#create as HTML
Parameters: {"images"=>{}}
Completed 400 Bad Request in 1ms (ActiveRecord: 6.0ms)
HTML
<input type="file" #selectFile id="selectFile" name="selectFile" class="btn btn-success">
<input type="button" class="btn btn-primary" (click)="uploadImage()" value="Upload Image">
upload-image.component.ts
export class UploadImageComponent{
#ViewChild('selectFile') selectFile;
constructor(private uploadService: UploadService) {}
uploadImage(){
let files = this.selectFile.nativeElement.files[0];
this.uploadService.uploadImage(files).subscribe(data => {return true},
error => {
console.log("error uploading image");
return Observable.throw(error);
})
}
upload-service.ts
#Injectable()
export class StallionService {
headers: Headers;
options: RequestOptions;
private imagesUrl = 'http://localhost:3000/images';
constructor(private http: Http) {
this.headers = new Headers({'Content-Type':'application/json'});
this.options = new RequestOptions({headers: this.headers});
}
uploadImage(image: File){
console.log(image);
const formData = new FormData();
formData.append("img", image);
console.log(formData);
return this.http.post(this.imagesUrl, formData,
this.options).map((res: Response) => res.json());
}
}
I can't see any errors in your code and i am a bit surprised that no params are shown in your rails console.
But i can show you how i did.
A simple solution (in Angular 2):
With the way you've set it up, i've found that it's very complex to save an image like that with Carrierwave and Angular.
A simpler solution is to use Base64 and the gem Carrierwave Base64 and Angulars FormGroup and FormController.
You could then use FileReader on change to set your file-input to the value:
<!-- part of a FormGroup called imageForm -->
<input formControlName="file" (change)="setImageFile($event)" type="file" id="selectFile" name="selectFile" class="btn btn-success">
And then in your component:
setImageFile(event) {
this.image_file = event.target.files[0];
if(this.image_file) {
const file_reader = new FileReader();
file_reader.readAsDataURL(this.image_file);
file_reader.onload = (e: any) => {
this.imageForm.get('file').setValue(file_reader.result);
}
}
}
And then on your upload you can send imageForm.value in your post request.
With the base64 gem you have to change one line (read on the GitHub page) but otherwise things will work as before and you are now able to save the image to the AWS etc..
I hope you get the idea - it worked for me! :)
Related
here I have a function that reacts to a button click
and gains a file from my backend.
onDownload() {
this.http.get('http://localhost:8080/backend/invoice/1/download',
{responseType: 'blob'})
.subscribe(res =>
console.log(res))
}
So far, I am happy because inside the chrome console I dont get any errors.
The response looks like this in the console:
The return type of the Java backend was InputStream (method annotation #Produces(MediaType.MULTIPART_FORM_DATA))
Then I found
https://stackblitz.com/edit/angular-blob-file-download?file=app%2Fapp.component.ts
and looked at ngOnInit() in app.component.ts
ngOnInit() {
const data = 'some text';
const blob = new Blob([data], { type: 'application/octet-stream' });
this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
}
Currently, I think my frontend receives a blob.
So, I can start in the line starting with "this.fileUrl="
and input my blob.
Inside the .html, I have a button to start the onDownload() function
and another tag to save the file on my local hard drive.
<div>
<button (click)="onDownload()">Herunterladen</button>
</div>
<a [href]="safeResourceUrl" download="file.txt">DownloadFile</a>
Meanwhile, I change the onDownload() method to
onDownload() {
this.http.get('http://localhost:8080/backend/invoice/1/download',
{responseType: 'blob'})
.subscribe(res => this.safeResourceUrl=this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(res)))
}
After I click "Herunterladen" and then the DownloadFile link I get either
a .txt file that I cannot read
or, if I change the file name to .pdf in the a tag inside the .html,
I get a "failed to load pdf document"
All I want is to get my original pdf that I stored in my database and that was sent from the backend.
Has anyone had the same problem before? Thank you for your help.
I changed my function to
onDownload() {
window.open(`http://localhost:8080/backend/invoice/${this.invoice.invoiceNr}/download`, 'blank');
}
Now it works:)
I am looking to create a file on the fly and offer a download link to the user in a GRAILS application.
I followed the approach from here. I have no errors however it doesn't seem to work. Here's my controller code.
`render (file: pptFile, fileName:'someppt.pptx', contentType: 'application/octet-stream')
Client side code makes an AJAX call to retrieve the file from server. It does not cause the server to force downloading of the file on the client (browser). Here's the client side code.
$.ajax({
type : 'POST',
url : '<<URL>>',
success: function(result) {
var uri = 'data:application/octet-stream;charset=UTF-8,' +
encodeURIComponent(result);
window.open(uri, 'somePPT.pptx');
},
failure: function(){
alert ('failure')
}
});
Perhaps something akin to this (paraphrased, but used for downloading a json file):
def someControllerMethod() {
def dlContent = someService.marshalJson()
def contentType = "application/octet-stream"
def filename = "someFilename.json"
response.setHeader("Content-Disposition", "attachment;filename=${filename}")
render(contentType: contentType, text: dlContent as JSON)
}
okay. So I finally got this to work. As proposed by #railsdog and many others (This problem has been discussed on other threads in stackoverflow but the specific case I had was slightly different from those) I ended up writing to response directly from server and took out the AJAX call. The only reason I was doing an AJAX call was because I did not want to submit the current page that had the "generate file" functionality (There are many data elements on the page and I did not want to re-do the entire page just for downloading the file). So I ended up using an anchor tag with target as "_blank". Here's the code snippet
<a href="myControllerMethodToGenerateFileAndWriteToHTTPResponseDirectlyAsSuggestedByOthersInThisPost"
target="_blank"/>
This actually opened a new page and did the submission to initiate the download. Problem solved. It's working fine in CHROME. :) Thanks guys!
I like the solution using the render method from #railsdog !
A slightly other approach which I used so far was:
def controllerMethod() {
...
File file = sepaXmlService.createTransfersFile(...)
response.setContentType("application/xml")
response.setHeader("Content-disposition", "attachment;filename=${file.getName()}")
OutputStream out = response.getOutputStream()
out.write(file.bytes)
out.close()
file.delete()
return
...
}
In the view I use the following statement in the form:
<g:actionSubmit action="controllerMethod" class="btn" value="Get XML!" /></td>
I think it should also be possible to use a
<g:link controller="foobar" action="controllerMethod" class="btn">GetXML</g:link>
I'm new to Angular. I've tried everything I know how and Google searches have surprisingly few tutorials on this particular question. Here's the last code I tried:
index.html
<form ng-submit="addArticle(articles)">
<input type="text" id="title" ng-model="newPost.title">
<input type="text" id="body" ng-model="newPost.body">
<input type="submit" value="Submit">
</form>
articles controller
app.controller('ArticlesCtrl', function($scope, Article) {
$scope.articles = Article.query();
$scope.newPost = Article.save();
});
articles service (rails backend)
app.factory('Article', function($resource) {
return $resource('http://localhost:3000/articles');
});
I can retrieve data just fine. But I can't submit any new data to the rails backend. On page load, the rails server error is:
Started POST "/articles" for 127.0.0.1 at 2015-02-08 18:26:29 -0800
Processing by ArticlesController#create as HTML
Completed 400 Bad Request in 0ms
ActionController::ParameterMissing (param is missing or the value is empty: article):
app/controllers/articles_controller.rb:57:in `article_params'
app/controllers/articles_controller.rb:21:in `create'
Pressing the submit button does nothing at all. The form basically does not work and the page is looking for a submission as soon as it loads.
I understand what the error says, that it's not receiving the parameters from the form. What I don't understand is what that should look like in my controller and/or form.
What am I doing wrong and how do I fix this?
Angular has a feature called services which acts as a model for the application. It's where I'm communicating with my Rails backend:
services/article.js
app.factory('Article', function($resource) {
return $resource('http://localhost:3000/articles/:id', { id: '#id'},
{
'update': { method: 'PUT'}
});
});
Even though the :id is specified on the end, it works just as well for going straight to the /articles path. The id will only be used where provided.
The rest of the work goes into the controller:
controllers/articles.js
app.controller('NewPostCtrl', function($scope, Article) {
$scope.newPost = new Article();
$scope.save = function() {
Article.save({ article: $scope.article }, function() {
// Optional function. Clear html form, redirect or whatever.
});
};
});
Originally, I assumed that the save() function that's made available through $resources was somewhat automatic. It is, but I was using it wrong. The default save() function can take up to four parameters, but only appears to require the data being passed to the database. Here, it knows to send a POST request to my backend.
views/articles/index.html
<form name="form" ng-submit="save()">
<input type="text" id="title" ng-model="article.title">
<input type="text" id="body" ng-model="article.body">
<input type="submit" value="Submit">
</form>
After getting the service setup properly, the rest was easy. In the controller, it's required to create a new instance of the resource (in this case, a new article). I created a new $scope variable that contains the function which invokes the save method I created in the service.
Keep in mind that the methods created in the service can be named whatever you want. The importance of them is the type of HTTP request being sent. This is especially true for any RESTful app, as the route for GET requests is the same as for POST requests.
Below is the first solution I found. Thanks again for the responses. They were helpful in my experiments to learn how this worked!
Original Solution:
I finally fixed it, so I'll post my particular solution. However, I only went this route through lack of information how to execute this through an angular service. Ideally, a service would handle this kind of http request. Also note that when using $resource in services, it comes with a few functions one of which is save(). However, this also didn't work out for me.
Info on $http: https://docs.angularjs.org/api/ng/service/$http
Info on $resource: https://docs.angularjs.org/api/ngResource/service/$resource
Tutorial on Services and Factories (highly useful): http://viralpatel.net/blogs/angularjs-service-factory-tutorial/
articles.js controller
app.controller('FormCtrl', function($scope, $http) {
$scope.addPost = function() {
$scope.article = {
'article': {
'title' : $scope.article.title,
'body' : $scope.article.body
}
};
// Why can't I use Article.save() method from $resource?
$http({
method: 'POST',
url: 'http://localhost:3000/articles',
data: $scope.article
});
};
});
Since Rails is the backend, sending a POST request to the /articles path invokes the #create method. This was a simpler solution for me to understand than what I was trying before.
To understand using services: the $resource gives you access to the save() function. However, I still haven't demystified how to use it in this scenario. I went with $http because it's function was clear.
Sean Hill has a recommendation which is the second time I've seen today. It may be helpful to anyone else wrestling with this issue. If I come across a solution which uses services, I'll update this.
Thank you all for your help.
I've worked a lot with Angular and Rails, and I highly recommend using AngularJS Rails Resource. It makes working with a Rails backend just that much easier.
https://github.com/FineLinePrototyping/angularjs-rails-resource
You will need to specify this module in your app's dependencies and then you'll need to change your factory to look like this:
app.factory('Article', function(railsResourceFactory) {
return railsResourceFactory({url: '/articles', name: 'article');
});
Basically, based on the error that you are getting, what is happening is that your resource is not creating the correct article parameter. AngularJS Rails Resource does that for you, and it also takes care of other Rails-specific behavior.
Additionally, $scope.newPost should not be Article.save(). You should initialize it with a new resource new Article() instead.
Until your input fields are blank, no value is stored in model and you POST empty article object. You can fix it by creating client side validation or set default empty string value on needed fields before save.
First of all you should create new Article object in scope variable then pass newPost by params or access directly $scope.newPost in addArticle fn:
app.controller('ArticlesCtrl', function($scope, Article) {
$scope.articles = Article.query();
$scope.newPost = new Article();
$scope.addArticle = function(newPost) {
if (newPost.title == null) {
newPost.title = '';
}
// or if you have underscore or lodash:
// lodash.defaults(newPost, { title: '' });
Article.save(newPost);
};
});
If you want use CRUD operations you should setup resources like below:
$resource('/articles/:id.json', { id: '#id' }, {
update: {
method: 'PUT'
}
});
I've got a REST api which assumes a multipartfile in the a post method.
Is there any way to do this kind of posts in Dart / AngularDart because all the solutions I've found so far are not working.
I've tried to use the http://dart-gde.github.io/dart-google-oauth2-library/multipart_file/MultipartFile.html solution, but it is not working in the browser because dart.io is not supported there.
My question is about the client side part directly from the browser. The serverside, which is written in Java can handle the post.
If you need multipart for file upload, all you have to do is send a FormData object using the HttpRequest class. Example:
import "dart:html";
...
var fileData; //file data to be uploaded
var formData = new FormData();
formData.append("field", "value"); //normal form field
formData.appendBlob("data", fileData); //binary data
HttpRequest.request("/service-url", method: "POST", sendData: formData).then((req) {
...
});
Furthermore, if you need to allow the user to upload a file from his hard disk, you have to use a html form with an <input type="file"> tag. Example:
Html file:
<form id="myForm" action="/service-url" method="POST" enctype="multipart/form-data">
<input type="text" name="field"> <!-- normal field -->
<input type="file" name="fileData"> <!-- file field -->
</form>
dart file:
var formData = new FormData(querySelector("#myForm"));
HttpRequest.request("/service-url", method: "POST", sendData: formData).then((req) {
...
});
I know this was asked a long time ago, but I've just had the same problem and the fix for me is the following (based on luizmineo's answer):
Use formData.appendBlob("data", fileData);
Don't set an explicit Content-Type header. This will get Dart to calculate the boundary section of the form-data which is crucial.
I finally found a way to post it as a multi-part form:
void uploadFiles() {
var formData = new FormData(querySelector("#fileForm"));
HttpRequest.request("/sp/file", method: "POST", sendData: formData).then((req) {
print("OK");
});
}
is used in conjunction with
<form id="fileForm" action="/sp/file" method="POST">
<input type="file" #upload (change)="uploadFiles(upload.files)"
(dragenter)="upload.style.setProperty('border', '3px solid green')"
(drop)="upload.style.setProperty('border', '2px dotted gray')" class="uploadDropZone" name="toUpload"/>
I want to upload files with AJAX. In the past I accomplished this by using the magical jQuery form plugin and it worked great. Currently I'm building a Rails app and trying to do things "The Rails Way" so I'm using the Form Helper and the paperclip gem to add file attachments.
The rails docs warn that the Form Helper does not work for AJAX file uploads:
Unlike other forms making an asynchronous file upload form is not as
simple as providing form_for with remote: true. With an Ajax form the
serialization is done by JavaScript running inside the browser and
since JavaScript cannot read files from your hard drive the file
cannot be uploaded. The most common workaround is to use an invisible
iframe that serves as the target for the form submission.
It seems clear there's no off-the-shelf solution. So I'm wondering what's the smartest thing to do. Seems like I have several options:
Use the form helper and the iframe trick.
Use the form helper + load jQuery form plugin to submit the file (not sure if this will play nice with Rails's authenticity token, etc)
Use the form helper + paperclip + [some other gem] to extend it's functionality to allow AJAX form submission.
All three seem possible. I know the least about #3, specifically the [some other gem] part. I found two similar questions (this and this) which mention a branch of Pic-Upload called Uploadify but those are both 2 years old and deal with Rails 2 and 3 (and Uploadify hasn't been updated in years). So given how much has changed, I think this is really a whole new question:
What's the best way to upload files with AJAX in Rails 4?
Have a look into the remotipart gem: https://github.com/JangoSteve/remotipart -- may get you all of the way there with very little work!
Using #rails/ujs.
view (.html.erb):
<%= file_field_tag :file, { id: "ajax_file_upload"} %>
controller(_controller.rb):
def update
#record = YourModel.find(params[:id])
respond_to do |format|
if #record.update_attributes(params[:your_model])
format.json { render json: { success: true } }
else
error_messages = #record.errors.messages.values.flatten
format.json { render json: { success: false, errors: error_messages } }
end
end
end
javascript(.js)
const uploadFile = element => {
const formData = new FormData();
formData.append("your_model[attribute_name]", element.target.files[0]);
Rails.ajax({
url: "your_model/:id",
type: "PUT",
beforeSend(xhr, options) {
options.data = formData;
return true;
},
success: response => {
if (response.success) {
alert("File uploaded successfully");
}
else {
alert(response.errors.join("<br>"));
}
},
error: () => {
alert("ajax send error");
}
});
};
const documentOnReady = () => {
const fileField = document.getElementById("ajax_file_upload");
if (fileField) {
fileField.addEventListener("change", uploadFile);
}
}
document.addEventListener("turbolinks:load", documentOnReady);
Note: No need to setRequestHeader in ajax while using FormData.
FormData uses the same format a form would use if the encoding type were set to "multipart/form-data"
IMHO Rails is not perfect when dealing with upload files using AJAX, especially if you want a progress bar. My suggestion is to use Javascript for the form submission over an AJAX request like you suggested in (2). If you are comfortable with Javascript you will not have many problems.
I recently used the same approach by using this very simple JS library https://github.com/hayageek/jquery-upload-file and I wrote more details here http://www.alfredo.motta.name/upload-video-files-with-rails-paperclip-and-jquery-upload-file/
For an application with a form to upload a movie with title and description the JS code looks like follow:
$(document).ready(function() {
var uploadObj = $("#movie_video").uploadFile({
url: "/movies",
multiple: false,
fileName: "movie[video]",
autoSubmit: false,
formData: {
"movie[title]": $('#movie_title').text(),
"movie[description]": $('#movie_description').text()
},
onSuccess:function(files,data,xhr)
{
window.location.href = data.to;
}
});
$("#fileUpload").click(function(e) {
e.preventDefault();
$.rails.disableFormElements($($.rails.formSubmitSelector));
uploadObj.startUpload();
});
});
Far from perfect, but gives you flexibility on your frontend.