guide blob streaming - devonfw/devon4ng GitHub Wiki

Angular File Uploading

This sample demonstrates how to upload a file to a server. For this, we will need to use an Angular form. In this case we have chosen a simple template-driven form, as the goal of this sample is just to show the process to upload a file. You can learn more about Forms in Angular in the official documentation.

Note
The back-end implementation for this sample is located here: devon4j-blob-streaming

FormData

FormData is an object where you can store key-value pairs that allows you to send through XMLHttpRequest. You can create a FormData object as simply as:

....
const formData = new FormData();
formData.append('key', value);
....

Let’s begin

I assume you already have your angular application running, if not, you can have a look to our AngularBasicPWA sample guide-angular-pwa

We are going to use Angular Material components, so it is necessary to install the dependency with the following command:

npm install --save @angular/material @angular/cdk @angular/animations

Importing necessary components

These are the component I am going to use for our sample, are material HTML components. For use the template-driven form you do not need to import any component. I am going to create a module called Core where I place the needed imports. After that, I will import Core module on my main App module, and I be able to use these components in any part of my application.

....
@NgModule({
  declarations: [],
  imports: [CommonModule],
  exports: [
    MatButtonModule,
    MatFormFieldModule,
    MatInputModule,
    FormsModule,
    MatProgressBarModule,
  ],
})
export class CoreModule {}
....

FormsModule Will allow us data binding through html and component.

The next step will be to create a component to place the uploading component: ng generate component uploader

So this will be our project structure so far:

folder structure

Then, in the app.component.html we need to add the selector for our new component, so it will be represented there. We are not going to create any route for this sample. We can also modify the values for the toolbar.

....
<div class="toolbar" role="banner">
  <img
    width="40"
    alt="Angular Logo"
    src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
  />
  <span>File uploader</span>
</div>

<app-uploader></app-uploader>

<router-outlet></router-outlet>
....

Now, our new component uploader will be loaded in the root page. Let’s add some code to it.

Uploader component

I will begin editing the html file. First thing we need is an input component, which will allow us to select the file to upload. Furthermore, I added a button which will be the responsible of calling the upload file window. Apart from this, there is also two labels and a progress bar. Labels will give feedback about file upload request, both with an if clause with uploadSuccess and uploadFail global variables that will be in uploader.component.ts. The progress bar will show the progress of the file being uploaded.

....
  <div class="upload">
    <div>
      <button mat-raised-button (click)="upload()">Upload file</button>
    </div>
        <label mat-label *ngIf="uploadSuccess"
      >The file was upload succesfully!</label
    >
    <label mat-label *ngIf="uploadFail"
      >There was an error uploading the file</label
    >
    <input
      type="file"
      #fileUpload
      name="fileUpload"
      accept="*"
      style="display: none"
    />
  </div>
    <mat-progress-bar
    *ngIf="fileInProgress"
    [value]="fileProgress"
  ></mat-progress-bar>
</div>
....

The button will call the upload() method in our uploader.component.ts, and as we can see, I assigned an identifier for the input, #fileUpload, so we can reference it from uploader.component.ts. It accepts any file, and the display none style is because it will be called when we click the button, so it is no necessary to be present in the view.

Our html view should look something similar to this:

html view 1

Let’s start in our .ts file. In order to interact with the input #fileUpload, it is necessary to declare it like this:

....
@ViewChild('fileUpload') fileUpload: ElementRef;
constructor() {}
....

And then, the upload() method that the button in html is calling:

....
 upload(): void {
    this.fileUpload.nativeElement.click();

    this.fileUpload.nativeElement.onchange = () => {
      const file = this.fileUpload.nativeElement.files[0];
      this.uploadFile(file);
    };
  }
....

The click method at first line will open the file explorer in order to select the desired file to upload, and on change method will be called when a new file is selected, so a change is detected. Then, uploadFile(…​) method will be called.

Before explain this uploadFile(…​) method, there is something still missing, a service to communicate with back-end through HTTP. I am going to place the service in a service folder inside our uploader component folder. Execute the following command ng generate service data and paste the following code

....
export class DataService {
  SERVER_URL = 'http://localhost:8081/services/rest/binary/v1/';

  constructor(private httpClient: HttpClient) {}

  uploadFile(formdData: FormData): Observable<HttpEvent<BinaryObject>> {
    const headers = new HttpHeaders({
      'Content-Type': 'multipart/form-data',
    });

    return this.httpClient.post<BinaryObject>(
      this.SERVER_URL + 'binaryobject',
      formdData,
      {
        headers,
        reportProgress: true,
        observe: 'events',
      }
    );
  }
}
....

We have declared the URL as a global variable. Also is necessary to set the content-type as multipart/form-data in the headers sections, that will be the body of the request. There is also two options to talk about:

  • reportProgress: to have a feedback about the file upload so we can show percentage on the view.

  • observe: ' events' in order to receive this type of events information.

In uploader.component.ts is missing uploadFile(…​) method.

....
  uploadFile(file: File): void {
    const formDataBody = this.getFormData(file);
    this.dataService.uploadFile(formDataBody).subscribe(
      (event) => {
        if (event.type === HttpEventType.UploadProgress) {
          this.fileProgress = Math.round((100 * event.loaded) / event.total);
        } else if (event instanceof HttpResponse) {
          this.fileInProgress = false;
          this.uploadSuccess = true;
        }
      },
      (err) => {
        console.log('Could not upload the file!');
        this.uploadFail = true;
      }
    );
  }
....

Notice that whether we have a correct response, or an error response, we set the variable this.uploadSuccess or this.uploadFail to show the labels in the html giving feedback. Once we call the service to do the HTTP request, we expect two types of response(three if we count the error), the first one is the progress of the upload, and will update the progress bar through this.fileProgress variable. The second one is a response when the request is finished. That is why the type of the response is checked between HttpEventType or HttpResponse.

Now, if you have your back-end running, you should be able to upload a file, and check in DB that all the process worked fine.

Note
Download method is not implemented yet.
⚠️ **GitHub.com Fallback** ⚠️