0

I am trying to upload multiple files using third party ngx-file-drop component in my Angular 7 application.

I am iterating through files object which is of the type NgxFileDropEntry. This object has only two properties called fileEntry and relativePath. While looping through the object however, I am able to assign selectedDocumentItem and selectedDate in HTML code.

These properties are visible during runtime in the component while debugging, but complains at compile time. I had used the above approach so that I could save records when the user clicks the upload button as all values are available via the object.

Before I can save the object, I need to populate the uploadDocument object with the values from the files object to pass it to the service.

I am not able to populate the uploadDocument object. Any issues with my logic. Is there an alternative to for loop that I am using?

<div class="upload-table">
    <table id="table1" class="center">

        <tbody class="upload-name-style">
            <tr *ngFor="let item of files; let i=index">
                <td> <input kendoTextBox [(ngModel)]="item.relativePath" style="width: 350px" /></td>
                <td>
                    <kendo-dropdownlist style="width:350px" [(ngModel)]="item.selectedDocumentItem" 
                        [data]="DocumentTypes" [defaultItem]="defaultItem" [filterable]="false" textField="Name"
                        valueField="Id">
                    </kendo-dropdownlist>
                </td>
                <td>
                    <kendo-datepicker style="width: 200px" [format]="'dd MMM, yyyy'"
                        [(ngModel)]="item.selectedDate"></kendo-datepicker>
                </td>
                <td> <button class="btn btn-default" (click)="deleteRow(i)"><i class="fa fa-trash"></i>Delete
                    </button></td>
            </tr>
        </tbody>
    </table>


</div>

JSON

[{"relativePath":"Simplex - Copy - Copy.xlsx","fileEntry":{"name":"Simplex - Copy - Copy.xlsx","isDirectory":false,"isFile":true},"selectedDocumentItem":{"Id":6,"Name":"Constitutional Documents"},"selectedDate":"2019-07-09T23:00:00.000Z"},{"relativePath":"Simplex - Copy (2).xlsx","fileEntry":{"name":"Simplex - Copy (2).xlsx","isDirectory":false,"isFile":true},"selectedDocumentItem":{"Id":10,"Name":"Manager Letters"},"selectedDate":"2019-07-13T23:00:00.000Z"},{"relativePath":"Simplex - Copy.xlsx","fileEntry":{"name":"Simplex - Copy.xlsx","isDirectory":false,"isFile":true},"selectedDocumentItem":{"Id":7,"Name":"Regulation / References"},"selectedDate":"2019-07-30T23:00:00.000Z"}]

Component

document: IDocument ;
uploadDocument: IDocument[] = [];
public files: NgxFileDropEntry[] = [];

  public createDocument() {
    console.log(this.files);

  this.files.forEach(element => {

        this.uploadDocument.forEach(doc => {

            doc.id = 5508,
            doc.documentTypeId = element.selectedDocumentItem.Id ,
            doc.name = element.relativePath,
            doc.documentDate = element.selectedDate

          });


    });

Attempted solution based on Darren's suggestion

html

 <div class="upload-table">
        <table id="table1" class="center">



            <tbody class="upload-name-style">
                <tr *ngFor="let item of documents; let i=index">
                    <td> <input kendoTextBox [(ngModel)]="item.fileDropEntry.relativePath" style="width: 350px" /></td>
                    <td>
                        <kendo-dropdownlist style="width:350px" [(ngModel)]="item.documentTypeId" 
                            [data]="DocumentTypes" [defaultItem]="defaultItem" [filterable]="false" textField="Name"
                            valueField="Id">
                        </kendo-dropdownlist>
                    </td>
                    <td>
                        <kendo-datepicker style="width: 200px" [format]="'dd MMM, yyyy'"
                            [(ngModel)]="item.documentDate"></kendo-datepicker>
                    </td>
                    <td> <button class="btn btn-default" (click)="deleteRow(i)"><i class="fa fa-trash"></i>Delete
                        </button></td>
                </tr>
            </tbody>
        </table>


    </div>

Component

    export interface IDocumentUpload {
        fileDropEntry: NgxFileDropEntry;
        id: number;
        name: string;
        documentTypeId: number;
        documentDate: Date;
    }

 documents : IDocumentUpload[] = [];

     public createDocument() {
            console.log(this.documents);
     }


 public dropped(files: NgxFileDropEntry[]) {
    this.files = files;
    for (const droppedFile of files) {

        // Is it a file?
        if (droppedFile.fileEntry.isFile) {
            const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
            fileEntry.file((file: File) => {

                // Here you can access the real file
                console.log(droppedFile.relativePath, file);
                console.log('Hello');

                /**
                // You could upload it like this:
                const formData = new FormData()
                formData.append('logo', file, relativePath)

                // Headers
                const headers = new HttpHeaders({
                  'security-token': 'mytoken'
                })

                this.http.post('https://mybackend.com/api/upload/sanitize-and-save-logo', formData, { headers: headers, responseType: 'blob' })
                .subscribe(data => {
                  // Sanitized logo returned from backend
                })
                **/

            });
        } else {
            // It was a directory (empty directories are added, otherwise only files)
            const fileEntry = droppedFile.fileEntry as FileSystemDirectoryEntry;
            console.log(droppedFile.relativePath, fileEntry);
        }
    }
}

1 Answer 1

1

TypeScript is a superset of JavaScript.

It exists simply to be a compile-time layer that enforces types (something that is lacking in vanilla JS). This is why you only get a compile-time error and not a runtime one.

Looking at the source for NgxFileDropEntry I can see that it does indeed only have two properties, namely relativePath and fileEntry so of course the compiler will complain when you try to access properties that do not exist on that type (the ones you added).

What I would suggest doing is create your own custom type to wrap the NgxFileDropEntry which also contains your custom properties. Something like this:

export interface SomeCustomNameThatMakeseSense {
  fileDropEntry: NgxFileDropEntry;
  documentItem: WhateverTheTypeOfThisIs;
  date: Date;
}

Then wherever you are getting your files from, you simple instantiate a new one of SomeCustomNameThatMakeseSense and use that in your HTML to add properties to.

EDIT:

It seems there has been some confusion so I want to clear things up by adding more of an explanation:

What I was suggesting for you to do, was to create new objects of type SomeCustomNameThatMakeseSense (from above) which wrap your files array. You don't want to be casting existing objects to this new type because that just hides the issue.

Change:

public files: NgxFileDropEntry[] = [];

To:

public files: SomeCustomNameThatMakeseSense[] = [];

Change:

public dropped(files: NgxFileDropEntry[]) {
  this.files = files;
 // Other code removed for the sake brevity 
}

To:

public dropped(files: NgxFileDropEntry[]) {
  this.files = files.map(file => ({
    fileDropEntry: file,
    documentItem: undefined, // Give this a default value of your own choice
    date: new Date() // Same here as above
  }));
  // Other code removed for the sake brevity 
}

Then in your HTML change:

*ngFor="let item of documents; let i=index"

To:

*ngFor="let item of files; let i=index"
Sign up to request clarification or add additional context in comments.

4 Comments

I don't understand your question but we aren't changing any types. We are creating new ones that wrap the previous ones which allows us to append some additional data (such as the date and document).
Oh ok. I am getting bit confused with the field names that you have used in the dropped event. Doent match the interface fields that i have defined
Oh, I used the one that I created in my post. Yes you have additional fields now in your edited answer so you will need to use those too. The point I am trying to make stays the same. You need to try to understand what it is we're doing here. Once you do, it won't matter what fields are used :) All that we are doing is taking an object of some type and wrapping it into a new type that we created ourselves. That's all. This new type simple has our own custom fields that we can use to logically add extra data to the original.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.