0

Django and Angular. I have models Recipe and Instructions. I have a dynamic table in Angular that I am using to build out the instructions. I want to be able to add a photo to each instruction. I am not sure how to build this array correctly...

I am using FormData() because thats the only way I have been able to upload images before. I also tried using an array of Files

models.py

class Recipe(models.Model):
    name = models.CharField(max_length=30)


class Instructions(models.Model):
    number = models.IntegerField()
    explanation = models.TextField(max_length=1000)
    recipe = models.ForeignKey(Recipe, related_name='instructions', on_delete=models.CASCADE)
    photo = models.ImageField(upload_to='test', null=True, blank=True)

component.ts

  newrecipe: RecipeFull = new RecipeFull
  instructionArray: Array<Instruction> = [];
  newinstruction: any = {}
  step:number
  fileData: File[]= [];
  imageData = new FormData()
  instructionArrayPhoto: Array<Instruction>= []

ngOnInit() {

    this.step = 1
    this.newinstruction = {number: this.step, explanation:"", photo: File };
    this.instructionArray.push(this.newinstruction);
  }


fileProgress(fileInput: any, row) {
      this.fileData[row] = <File>fileInput.target.files[0];
      this.imageData.append(row, this.fileData[row])
}
 

  submitrecipe(){
    this.newrecipe.instructions = this.instructionArray //this should be the image array

    //NOT SURE WHAT TO DO HERE OR HOW TO CREATE THE ARRAY WITH IMAGES...
    this.newrecipe.instructions[0].photo = this.imageData[0]
    this.recipeservice.addrecipe(this.newrecipe).subscribe()
  }

html

        <td>
            <h3>{{instructionArray[i].number}}</h3>
        </td>
        <td>
            <input [(ngModel)]="instructionArray[i].explanation" class="form-control" type="text" />{{i}}
        </td>
        <td>
            <div>
                <div class="form-group">
                    <input type="file" name="image" (change)="fileProgress($event, i)" />
                </div>
            </div>
        </td>
ratrace123
  • 976
  • 4
  • 12
  • 24

1 Answers1

0

If you ensure these images are small under 5Mb, you can convert these images to Base64Encoding and then attaching them into your JSON body. It is simplest way to handle complexity multiple files with many constraints. However, I never recommend this way because it is weird and heavy (over 33% string size than binary file). You can do that by the following code:

// This interface is representing your response model from Upload File API
export interface DownloadableResponseFile {
    fileName: string,
    downloadableUrl: string
}

// files is Set<File> or File[] that you got from fileInput.target.files
function uploadMulti(files: Set<File>, url){

 const status: {
            [key: string]: {
                progress: Observable<number>,
                completed: Observable<DownloadableResponseFile>,
                error: Observable<any>
            }
        } = {};

files.forEach(file => {
            const formData: FormData = new FormData();
            formData.append('formFile', file, file.name);
            const req = new HttpRequest('POST', url_, formData, {
                reportProgress: true
            });
            const progress = new Subject<number>();
            const completed = new Subject<DownloadableResponseFile>();
            const error = new Subject<any>()
            this.http.request(req).subscribe(event => {
                if (event.type === HttpEventType.UploadProgress) {
                    const percentDone = Math.round(100 * event.loaded / event.total);
                    progress.next(percentDone);
                } else if (event instanceof HttpResponse) {
                    progress.next(100);
                    progress.complete();

                    completed.next({
                        fileName: file.name,
                        response: event.body as ResponseUploadFile
                    })
                    completed.complete()
                }
            },
                err => {
                    error.next(err)
                });
            status[file.name] = {
                progress: progress.asObservable(),
                completed: completed.asObservable(),
                error: error.asObservable()
            };
        });
  return status;
}

// Assume this is a main function on your component to handle upload files
onFileChanges($event){

     const status = uploadMulti($event.target.files, 'http://localhost/upload')
     const uploadingProgressObserables = []
     for (const key in status) {
         uploadingProgressObserables.push(this.progress[key].completed);
     }

     // use forkJoins to listen all completed upload files
     forkJoin(uploadingProgressObserables).subscribe((responseFiles: any[]) => {
         responseFiles.forEach(response: DownloadableResponseFile => {
             // Do something with response body
         })
     });
}
Johnathan Le
  • 675
  • 3
  • 8
  • so you are saying that I should not do it this way? I am fine to have the images be under 5Mb – ratrace123 Jun 24 '20 at 10:55
  • These code above are using upload multiple files with counting completing percentage via Multipart FormData. I have mentioned about the simplest way in your cases. You can encode file to Base64Encoding string and upload to Backend but it is heavy because Base64Encoding string is over 33% size than binary file. https://stackoverflow.com/questions/42482951/converting-an-image-to-base64-in-angular-2 – Johnathan Le Jun 24 '20 at 14:40