0

My view has some simple code:

  HERE{{ files.length }}THERE
  <div class="uploaded-files" *ngIf="files.length > 0">
    <div class="uploaded-files-title">
      <h4>Selected Files</h4><h5>&nbsp;(X)</h5>
    </div>
    <table class="table">
      <thead class="thead-dark">
      <tr>
        <th scope="col">File Name</th>
        <th scope="col">File Size</th>
        <th scope="col" class="text-center">Upload Status</th>
        <th scope="col" class="text-center"><a href="" (click)="removeAll($event)">Remove All</a></th>
      </tr>
      </thead>
      <tbody>
        <tr *ngFor="let file of files">
          <td>{{ file.relativePath }}</td>
          <td>{{file.size}}</td>
          <td class="text-center"><i class="mdi mdi-check-circle mdi-24px approved"></i></td>
          <td class="text-center"><i class="mdi mdi-delete mdi-24px actions"></i></td>
        </tr>

My component is:

import {
  Component,
  OnInit
} from '@angular/core';
import { UploadEvent, UploadFile, FileSystemFileEntry  } from 'ngx-file-drop';


@Component({
  selector: 'upload-modal',  // <upload-modal></upload-modal>
  providers: [
  ],
  styleUrls: [ './upload.component.scss' ],
  templateUrl: './upload.component.html'
})
export class UploadComponent implements OnInit {


  constructor() {
  }

  public files: UploadFile[] = [];

  public ngOnInit() {
  }

  dropFile(event) {
    let droppedFiles: UploadFile[] = []
    if(this.files.length === 1) {
      return
    }

    const fileEntry = event.files[0].fileEntry as FileSystemFileEntry;
    fileEntry.file(fileData => {
      console.log('before', this.files)
      this.files.push({
        name: fileEntry.name,
        size: fileData.size
      })
      console.log('after', this.files)
    })
  }

  handleFileInput() {
    alert('files')
  }

  removeAll(event) {
    event.stopPropagation()
    this.files: UploadFile[] = []
  }

}

When my component's dropFile function does what it does, the console prints out correctly, but the view doesn't have any updated files.

I'm using angular 5.2.0. What am I doing wrong?

Narendra Jadhav
  • 10,052
  • 15
  • 33
  • 44
Shamoon
  • 41,293
  • 91
  • 306
  • 570
  • so you're *ngIf never changes to true to display the content? but you're console.log('after', this.files) shows that it has length greater than 0 is that correct? – Woot Mar 19 '18 at 20:37
  • @Woot that is correct. – Shamoon Mar 19 '18 at 20:38
  • am I correct in assuming the that Here {{ files.length }} There section of your code does display the correct length? – Woot Mar 19 '18 at 20:40
  • It does not. It shows 0 – Shamoon Mar 19 '18 at 20:41
  • 1
    what triggers the dropFile function? I don't see that in your component. If that function isn't triggered by your component then I would guess that angular doesn't fire the change detection process when the function is called. Also I noticed that you component implements OnChanges but you don't import it and you don't implement it. That could cause issues. – Woot Mar 19 '18 at 20:45
  • It's triggered from https://www.npmjs.com/package/ngx-file-drop – Shamoon Mar 19 '18 at 22:31
  • 1
    Are you sure you don’t have console errors? You’re defining your class to implement OnChanges but you are not implementing the method. Not sure how is this even compiling. – Hugo Noro Mar 19 '18 at 22:34
  • try that: `this.files = [...this.files, { name: fileEntry.name, size: fileData.size }]` instead of using `push` – David Mar 20 '18 at 09:13

4 Answers4

2

I think Angular's not aware of you having changed the model behind the scenes.

By default, Angular uses zones to trigger it's change detection mechanism after async events—although this works fine for most async events you'll encounter, the zone implementation used by Angular, Zone.js, only supports a handful of non-standard APIs and FileSystemFileEntry isn't one of them. In cases like this, your best bet is to manually trigger the change detection as explained here.

  • I'm using https://www.npmjs.com/package/ngx-file-drop to get the `FileSystemFileEntry`. I tried with zones - no dice. – Shamoon Mar 19 '18 at 22:50
  • It's strange that manually triggering the change detection's not working—in the [official demo](https://georgipeltekov.github.io/) of the library they do exactly and it looks like it's working fine. –  Mar 20 '18 at 06:34
  • If you to it like in the official demo i.e. inject a `ChangeDetectorRef` into the `constructor` and call it's `detectChanges()` method in the last line in the `file` callback (**not** the last line of your `dropFile` method) I think it should work fine. Doesn't it? –  Mar 20 '18 at 06:44
1

You can manually detect changes using the change detection. I don't recommend using this method in all situations but in this one it's probably the best way.

Add ChangeDectorRef to the import statement

import {
  Component,
  OnInit,
  ChangeDetectorRef 
} from '@angular/core';

then add it to your constructor

constructor(private cd: ChangeDetectorRef) {}

Then after you drop the files you can use the change detector to trigger change detection.

dropFile(event) {

  // add this to the end of the dropFile method
  this.cd.detectChanges();
}
Woot
  • 1,759
  • 1
  • 16
  • 23
0

The change detector needs to be the last line of the callback as user4091335 pointed out and not the last line of the droppped file method.

dropped(event: any) {
    for (const droppedFile of event.files) {
      const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
      fileEntry.file((file: File) => {
        this.files.push(file);
        **this.ref.detectChanges();**
      })
    }
  }
dc922
  • 629
  • 3
  • 14
  • 27
0

I had the same issue trying to update the view, and I solved using promises instead of detecting the changes on the view using ChangeDetectorRef. Worked like a charm. Here's my solution

    triggerUpload(evt:UploadEvent){    


    let files = evt.files;
    let self = this;
    this.fillupFiles(files).then(function(response){
       self.uploadAndRender();          
    });

  }

fillupFiles(files):Promise<Object>{
    let self = this;
    let promise = new Promise(function(resolve, reject){
      var count = files.length;
      var index = 0;
      for(let droppedFile of files){
        if(droppedFile.fileEntry.isFile){
          let fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
          fileEntry.file((file:File)=>{
            index++;
            self.files.push(file);
            if(index==count){
              resolve(true);
            }
          });
        }
      }
    });

    return promise;
  }