Angular & ASP.NET Web API Multiple File Uploads

Table of Contents
Overview
The API is available here and included in the Github Repository here.
The following uses Angular with Bootstrap (To keep it simple) and ASP.NET Web API to create/delete a record and 1 to 3 images. A title is saved in the parent table (Products) and the images are saved to a folder and the file name, size, mime type, and short path (unnecessary) in another table (ProductImages). The main take away from this, is to create and save the Parent and Child records in one API call using a form with a drop zone.
Below is an overview/diagram of what this example does. Code for the UI and API can be found on GitHub.

Install the following libraries
npm i bootstrap
npm i ngx-dropzone
Generate a service with ng g s services/image-file
Inside the service add the following promise call
constructor(private http: HttpClient) { }
addProductImages(fileForm: any, files: any): string {
const URL = `${environment.baseUrl}/api/product/add`;
const formData = new FormData();
// Add Record Title
formData.append('Title', fileForm.Title);
// Add the file
for (let i = 0; i < files.length; i++) {
formData.append(`images[${i}]`, files[i])
}
// formData.append('Description', fileForm.Description);
let status = '';
// Use a promise for this example
const promise = new Promise((resolve, reject) => {
this.http.post(URL, formData)
.toPromise()
.then(
res => { // Success
console.log(res);
status = 'resolved';
}
)
.catch((err) => {
console.error(err);
status = 'rejected';
});
});
return status;
}
getImages(): Observable<ResultsObj> {
const URL = `${environment.baseUrl}/api/product/get`;
console.log(URL);
return this.http.get(URL);
}
removeProduct(id: number): Observable<ResultsObj> {
const URL = `${environment.baseUrl}/api/product/remove/${id}`;
console.log(URL);
return this.http.delete(URL);
}
The key to uploading all the files in the service is the following:
for (let i = 0; i < files.length; i++) {
formData.append(`images[${i}]`, files[i])
}
Use a promise not an observable, since the API action is asynchronous
const promise = new Promise((resolve, reject) => {
this.http.post(URL, formData)
.toPromise()
.then(
res => { // Success
console.log(res);
status = 'resolved';
}
)
.catch((err) => {
console.error(err);
status = 'rejected';
});
});
Component HTML form
There is a title field with the ngx-dropzone component and a submit button.

<div class="card" >
<div class="card-body">
<div class="row mb-5">
<div class="col-12">
<h5>Upload Image</h5>
<form class="post-form" method="POST" (ngSubmit)="onSubmit($event)" [formGroup]="imageForm" >
<div class="row">
<div class="col-12">
<label>Parent Table ID</label>
<input type="text" class="form-control" placeholder="Title" name="Title" formControlName="Title">
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<div class="custom-dropzone" ngx-dropzone [accept]="'image/*'" (change)="onSelect($event)">
<ngx-dropzone-label>
<div>
<h2>Dropzone</h2>
<h5>Drag & Drop Files Here</h5>
</div>
</ngx-dropzone-label>
<ngx-dropzone-image-preview ngProjectAs="ngx-dropzone-preview" *ngFor="let f of files" [file]="f" [removable]="true" (removed)="onRemove(f)" >
<ngx-dropzone-label>{{ f.name }} ({{ f.type }})</ngx-dropzone-label>
</ngx-dropzone-image-preview>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col">
<button class="btn btn-primary" type="submit" [disabled]="!imageForm.valid">Save Image & Title</button>
</div>
</div>
</form>
</div>
</div>
Component TypeScript File
This component will have onSelect and onRemove methods for ngx-dropzone. onSubmit will call the addProductImages() service, alert the parent to update the list, and reset the form.
export class FileFormComponent implements OnInit, OnDestroy {
@Output() onSubmitForm = new EventEmitter<any>();
private subs = new Subscription();
private imageFormData = new MediaImageClass();
public message: string;
public imageForm: FormGroup;
public files: File[] = [];
constructor(private fb: FormBuilder,
private fileSVC: ImageFileService) { }
ngOnInit() {
this.imageForm = this.fb.group({
Title: ['', [Validators.required]],
});
}
ngOnDestroy(): void {
if (this.subs) {
this.subs.unsubscribe();
}
}
onSubmit($event) {
this.imageFormData.Title = this.imageForm.controls.Title.value;
this.fileSVC.addProductImages(this.imageFormData, this.files);
this.onSubmitForm.emit(this.imageForm);
this.imageForm.reset();
this.imageForm.controls.Title.setValue('');
this.imageForm.controls.Title.setErrors(null);
}
// Dropzone related
onSelect(event) {
console.log(event);
this.files.push(...event.addedFiles);
}
onRemove(event) {
console.log(event);
this.files.splice(this.files.indexOf(event), 1);
}
}
Remember to get the solution from here.

You must be logged in to post a comment.